@@ -12,15 +12,17 @@ use std::{
12
12
13
13
use askama:: Template ;
14
14
use axum:: {
15
- extract:: { Path , Query , State } ,
15
+ body:: Body ,
16
+ extract:: { Path , Query , Request , State } ,
16
17
handler:: Handler ,
17
18
http:: {
18
- header:: { CACHE_CONTROL , CONTENT_SECURITY_POLICY , CONTENT_TYPE } ,
19
+ header:: { ACCEPT , CACHE_CONTROL , CONTENT_SECURITY_POLICY , CONTENT_TYPE } ,
19
20
HeaderName , HeaderValue , StatusCode ,
20
21
} ,
22
+ middleware:: Next ,
21
23
response:: { IntoResponse , Redirect , Response } ,
22
24
routing:: get,
23
- Router ,
25
+ Extension , Router ,
24
26
} ;
25
27
use axum_extra:: routing:: RouterExt ;
26
28
use base64:: { prelude:: BASE64_STANDARD , Engine } ;
@@ -94,6 +96,7 @@ async fn main() {
94
96
let noindex = SetResponseHeaderLayer :: overriding ( ROBOTS_NAME . clone ( ) , ROBOTS_VALUE . clone ( ) ) ;
95
97
let csp = SetResponseHeaderLayer :: overriding ( CONTENT_SECURITY_POLICY , CSP_VALUE . clone ( ) ) ;
96
98
let clacks = SetResponseHeaderLayer :: overriding ( CLACKS_NAME . clone ( ) , CLACKS_VALUE . clone ( ) ) ;
99
+ let error_handler = axum:: middleware:: from_fn_with_state ( state. clone ( ) , error_middleware) ;
97
100
98
101
let serve_dir_raw = ServeDir :: new ( & asset_dir)
99
102
. append_index_html_on_directories ( true )
@@ -127,7 +130,12 @@ async fn main() {
127
130
)
128
131
. fallback_service ( serve_dir)
129
132
. merge ( api)
130
- . layer ( ServiceBuilder :: new ( ) . layer ( csp) . layer ( clacks) )
133
+ . layer (
134
+ ServiceBuilder :: new ( )
135
+ . layer ( csp)
136
+ . layer ( clacks)
137
+ . layer ( error_handler) ,
138
+ )
131
139
. with_state ( state) ;
132
140
133
141
let socket_address = SocketAddr :: from ( ( Ipv4Addr :: UNSPECIFIED , port) ) ;
@@ -227,15 +235,10 @@ pub struct PingPageTemplate {
227
235
async fn ping_page (
228
236
State ( state) : State < AppState > ,
229
237
Path ( ( edition, hostname) ) : Path < ( String , String ) > ,
230
- ) -> Result < PingPageTemplate , ErrorTemplate > {
238
+ ) -> Result < PingPageTemplate , Failure > {
231
239
match edition. as_str ( ) {
232
240
"java" | "bedrock" => { }
233
- _ => {
234
- return Err ( ErrorTemplate :: from_failure (
235
- & Failure :: UnknownEdition ,
236
- & state,
237
- ) )
238
- }
241
+ _ => return Err ( Failure :: UnknownEdition ) ,
239
242
}
240
243
Ok ( PingPageTemplate {
241
244
svc_status : * state. svc_response . read ( ) ,
@@ -268,10 +271,8 @@ pub struct PingFrameTemplate {
268
271
async fn ping_frame (
269
272
State ( state) : State < AppState > ,
270
273
Path ( ( edition, hostname) ) : Path < ( String , String ) > ,
271
- ) -> Result < PingFrameTemplate , ErrorTemplate > {
272
- let ping = ping_generic ( & edition, hostname. clone ( ) )
273
- . await
274
- . map_err ( |v| v. as_error_template ( & state) ) ?;
274
+ ) -> Result < PingFrameTemplate , Failure > {
275
+ let ping = ping_generic ( & edition, hostname. clone ( ) ) . await ?;
275
276
Ok ( PingFrameTemplate {
276
277
ping,
277
278
root_url : state. root_url ,
@@ -294,10 +295,8 @@ pub struct PingElementTemplate {
294
295
async fn ping_markup (
295
296
State ( state) : State < AppState > ,
296
297
Path ( ( edition, hostname) ) : Path < ( String , String ) > ,
297
- ) -> Result < PingElementTemplate , ErrorTemplate > {
298
- let ping = ping_generic ( & edition, hostname. clone ( ) )
299
- . await
300
- . map_err ( |v| v. as_error_template ( & state) ) ?;
298
+ ) -> Result < PingElementTemplate , Failure > {
299
+ let ping = ping_generic ( & edition, hostname. clone ( ) ) . await ?;
301
300
Ok ( PingElementTemplate {
302
301
ping,
303
302
bd : state. bust_dir ,
@@ -377,17 +376,7 @@ impl IntoResponse for Failure {
377
376
Self :: NoHostname | Self :: UnknownEdition => StatusCode :: BAD_REQUEST ,
378
377
} ;
379
378
error ! ( error = ?self , "Error processing request" ) ;
380
- let ser = ErrorSerialization {
381
- error : self . to_string ( ) ,
382
- } ;
383
- ( status, Json ( ser) ) . into_response ( )
384
- }
385
- }
386
-
387
- impl Failure {
388
- #[ must_use]
389
- pub fn as_error_template ( & self , state : & AppState ) -> ErrorTemplate {
390
- ErrorTemplate :: from_failure ( self , state)
379
+ ( status, Extension ( Arc :: new ( self ) ) , Body :: empty ( ) ) . into_response ( )
391
380
}
392
381
}
393
382
@@ -404,27 +393,23 @@ pub struct ErrorTemplate {
404
393
root_url : Arc < str > ,
405
394
}
406
395
407
- impl ErrorTemplate {
408
- fn from_failure ( failure : & Failure , state : & AppState ) -> Self {
409
- Self {
410
- root_url : state. root_url . clone ( ) ,
411
- bd : state. bust_dir . clone ( ) ,
412
- error : failure. to_string ( ) ,
413
- }
414
- }
415
- }
396
+ static HTML_CTYPE : HeaderValue = HeaderValue :: from_static ( "text/html;charset=utf-8" ) ;
416
397
417
398
pub struct Json < T : Serialize > ( pub T ) ;
418
399
400
+ static JSON_CTYPE : HeaderValue = HeaderValue :: from_static ( "application/json;charset=utf-8" ) ;
401
+
402
+ fn infallible_json_serialize < T : Serialize > ( data : & T ) -> Vec < u8 > {
403
+ serde_json:: to_vec_pretty ( data) . unwrap_or_else ( |_| {
404
+ r#"{"error": "JSON Serialization failed, please make a bug report"}"#
405
+ . as_bytes ( )
406
+ . to_vec ( )
407
+ } )
408
+ }
409
+
419
410
impl < T : Serialize > IntoResponse for Json < T > {
420
411
fn into_response ( self ) -> Response {
421
- static JSON_CTYPE : HeaderValue = HeaderValue :: from_static ( "application/json;charset=utf-8" ) ;
422
-
423
- let body = serde_json:: to_vec_pretty ( & self . 0 ) . unwrap_or_else ( |_| {
424
- r#"{"error": "JSON Serialization failed, please make a bug report"}"#
425
- . as_bytes ( )
426
- . to_vec ( )
427
- } ) ;
412
+ let body = infallible_json_serialize ( & self . 0 ) ;
428
413
(
429
414
StatusCode :: INTERNAL_SERVER_ERROR ,
430
415
[ ( CONTENT_TYPE , JSON_CTYPE . clone ( ) ) ] ,
@@ -434,6 +419,34 @@ impl<T: Serialize> IntoResponse for Json<T> {
434
419
}
435
420
}
436
421
422
+ async fn error_middleware ( State ( state) : State < AppState > , req : Request , next : Next ) -> Response {
423
+ let json = req
424
+ . headers ( )
425
+ . get ( ACCEPT )
426
+ . is_some_and ( |v| v. to_str ( ) . is_ok_and ( |v| v. contains ( "application/json" ) ) ) ;
427
+ let mut resp = next. run ( req) . await ;
428
+ if let Some ( failure) = resp. extensions ( ) . get :: < Arc < Failure > > ( ) . cloned ( ) {
429
+ let error = failure. to_string ( ) ;
430
+ if json {
431
+ resp. headers_mut ( ) . insert ( CONTENT_TYPE , JSON_CTYPE . clone ( ) ) ;
432
+ let error = ErrorSerialization { error } ;
433
+ let json = infallible_json_serialize ( & error) ;
434
+ * resp. body_mut ( ) = Body :: from ( json) ;
435
+ } else {
436
+ resp. headers_mut ( ) . insert ( CONTENT_TYPE , HTML_CTYPE . clone ( ) ) ;
437
+ let error = ErrorTemplate {
438
+ error,
439
+ bd : state. bust_dir ,
440
+ root_url : state. root_url ,
441
+ }
442
+ . render ( )
443
+ . unwrap_or_else ( |e| format ! ( "error rendering template: {e}" ) ) ;
444
+ * resp. body_mut ( ) = Body :: from ( error) ;
445
+ } ;
446
+ }
447
+ resp
448
+ }
449
+
437
450
pub struct Png ( pub Vec < u8 > ) ;
438
451
439
452
impl IntoResponse for Png {
0 commit comments