1- use std:: error:: Error as StdError ;
1+ use std:: { cell :: RefCell , error:: Error as StdError } ;
22
33use crate :: routing:: RequestType ;
44use axum:: response:: { IntoResponse , Response } ;
@@ -12,7 +12,6 @@ use ic_bn_lib::{
1212} ;
1313use ic_http_gateway:: HttpGatewayError ;
1414use ic_transport_types:: RejectCode ;
15- use std:: sync:: Arc ;
1615use strum:: { Display , IntoStaticStr } ;
1716use tokio:: task_local;
1817
@@ -21,11 +20,12 @@ use super::ic::BNResponseMetadata;
2120#[ derive( Default , Clone ) ]
2221pub struct ErrorContext {
2322 pub request_type : RequestType ,
23+ pub authority : Option < FQDN > ,
2424 pub alternate_error_domain : Option < FQDN > ,
2525}
2626
2727task_local ! {
28- pub static ERROR_CONTEXT : Arc <ErrorContext >;
28+ pub static ERROR_CONTEXT : RefCell <ErrorContext >;
2929}
3030
3131const ERROR_PAGE_TEMPLATE : & str = include_str ! ( "error_pages/template.html" ) ;
@@ -36,6 +36,8 @@ const RETRY_LOGIC: &str = include_str!("error_pages/components/retry_logic.js");
3636const APPEAL_SECTION : & str = include_str ! ( "error_pages/components/appeal_section.html" ) ;
3737
3838const ALTERNATE_ERROR : & str = include_str ! ( "error_pages/caffeine_error.html" ) ;
39+ const ALTERNATE_ERROR_UNKNOWN_DOMAIN : & str =
40+ include_str ! ( "error_pages/caffeine_error_unknown_domain.html" ) ;
3941
4042const CANISTER_ERROR_SVG : & str = include_str ! ( "error_pages/assets/canister-error.svg" ) ;
4143const CANISTER_WARNING_SVG : & str = include_str ! ( "error_pages/assets/canister-warning.svg" ) ;
@@ -518,29 +520,37 @@ impl IntoResponse for ErrorClientFacing {
518520 fn into_response ( self ) -> Response {
519521 let context = ERROR_CONTEXT
520522 . try_with ( |ctx| ctx. clone ( ) )
521- . unwrap_or_else ( |_| Arc :: new ( ErrorContext :: default ( ) ) ) ;
523+ . unwrap_or_default ( ) ;
524+ let context = context. borrow ( ) ;
522525
523526 let error_data = self . data ( ) ;
524527
525528 // Return an HTML error page if it was an HTTP request
526529 let body = match context. request_type {
527- RequestType :: Http => match self {
528- Self :: UnknownDomain ( domain) => {
529- // Check if we have a configured alternate error domain and if the current domain is a subdomain of it
530- if context. alternate_error_domain . as_ref ( ) . is_some_and (
531- |alternate_error_domain| domain. is_subdomain_of ( alternate_error_domain) ,
532- ) {
533- ALTERNATE_ERROR . to_string ( )
534- } else {
535- error_data. html ( )
530+ RequestType :: Http => {
531+ // Check if this is an alternate error domain
532+ // and produce alternate errors then
533+ if context
534+ . alternate_error_domain
535+ . as_ref ( )
536+ . zip ( context. authority . as_ref ( ) )
537+ . map ( |( alternate, authority) | authority. is_subdomain_of ( alternate) )
538+ == Some ( true )
539+ {
540+ match self {
541+ Self :: UnknownDomain ( _) => ALTERNATE_ERROR_UNKNOWN_DOMAIN ,
542+ _ => ALTERNATE_ERROR ,
536543 }
544+ . to_string ( )
545+ } else {
546+ error_data. html ( )
537547 }
538- _ => error_data . html ( ) ,
539- } ,
548+ }
549+
540550 _ => format ! ( "error: {}\n details:\n {}" , self , error_data. description) ,
541551 } ;
542552
543- // build the final response
553+ // Build the final response
544554 let mut resp = ( error_data. status_code , body) . into_response ( ) ;
545555 if context. request_type == RequestType :: Http {
546556 resp. headers_mut ( ) . insert ( CONTENT_TYPE , CONTENT_TYPE_HTML ) ;
@@ -619,13 +629,15 @@ impl ErrorData {
619629#[ cfg( test) ]
620630mod test {
621631 use super :: * ;
632+ use fqdn:: fqdn;
622633 use http:: HeaderMap ;
634+ use http_body_util:: BodyExt ;
623635 use ic_bn_lib:: { http:: headers:: X_IC_ERROR_CAUSE , hval, ic_agent:: AgentError } ;
624636 use ic_transport_types:: RejectResponse ;
625637 use std:: sync:: Arc ;
626638
627- #[ test]
628- fn test_error_cause ( ) {
639+ #[ tokio :: test]
640+ async fn test_error_cause ( ) {
629641 // Mapping of Rustls errors
630642 let err = anyhow:: Error :: new ( rustls:: Error :: NoCertificatesPresented ) ;
631643 assert ! ( matches!(
@@ -743,6 +755,52 @@ mod test {
743755 assert ! ( matches!(
744756 ErrorCause :: from( http_gw_error) ,
745757 ErrorCause :: BackendError ( _)
746- ) )
758+ ) ) ;
759+
760+ // Test alternate errors
761+ let context = RefCell :: new ( ErrorContext {
762+ alternate_error_domain : Some ( fqdn ! ( "caffeine.ai" ) ) ,
763+ request_type : RequestType :: Http ,
764+ authority : Some ( fqdn ! ( "foobar.caffeine.ai" ) ) ,
765+ } ) ;
766+
767+ let error = ErrorCause :: UnknownDomain ( fqdn ! ( "foo" ) ) ;
768+ let error: ErrorClientFacing = ( & error) . into ( ) ;
769+
770+ ERROR_CONTEXT
771+ . scope ( context. clone ( ) , async move {
772+ let resp = error. into_response ( ) ;
773+ let body = resp. into_body ( ) . collect ( ) . await . unwrap ( ) . to_bytes ( ) ;
774+ assert_eq ! ( body, ALTERNATE_ERROR_UNKNOWN_DOMAIN . as_bytes( ) ) ;
775+ } )
776+ . await ;
777+
778+ let error = ErrorCause :: CanisterError ;
779+ let error: ErrorClientFacing = ( & error) . into ( ) ;
780+
781+ ERROR_CONTEXT
782+ . scope ( context, async move {
783+ let resp = error. into_response ( ) ;
784+ let body = resp. into_body ( ) . collect ( ) . await . unwrap ( ) . to_bytes ( ) ;
785+ assert_eq ! ( body, ALTERNATE_ERROR . as_bytes( ) ) ;
786+ } )
787+ . await ;
788+
789+ let context = RefCell :: new ( ErrorContext {
790+ alternate_error_domain : Some ( fqdn ! ( "caffeine.ai" ) ) ,
791+ request_type : RequestType :: Http ,
792+ authority : Some ( fqdn ! ( "foobar.cocaine.ai" ) ) ,
793+ } ) ;
794+
795+ let error = ErrorCause :: UnknownDomain ( fqdn ! ( "foo" ) ) ;
796+ let error: ErrorClientFacing = ( & error) . into ( ) ;
797+
798+ ERROR_CONTEXT
799+ . scope ( context. clone ( ) , async move {
800+ let resp = error. into_response ( ) ;
801+ let body = resp. into_body ( ) . collect ( ) . await . unwrap ( ) . to_bytes ( ) ;
802+ assert_ne ! ( body, ALTERNATE_ERROR_UNKNOWN_DOMAIN . as_bytes( ) ) ;
803+ } )
804+ . await ;
747805 }
748806}
0 commit comments