@@ -23,10 +23,12 @@ pub use schema::{
2323} ;
2424
2525pub ( crate ) use parse:: RowsetParseError ;
26- pub ( crate ) use query_scoped:: { QueryScopedError , QueryScopedRepr , QueryScopedResult } ;
26+ pub ( crate ) use query_scoped:: {
27+ QueryScopedError , QueryScopedRepr , QueryScopedResult , with_optional_query_id,
28+ } ;
2729pub ( crate ) use repr:: {
28- AuthError , ConfigError , InternalError , NetworkError , ProtocolError , ServerError ,
29- SessionExpiredError , TimeoutError ,
30+ AuthError , CancelledError , ConfigError , InternalError , NetworkError , ProtocolError ,
31+ ServerError , SessionExpiredError , TimeoutError ,
3032} ;
3133
3234const VALUE_PREVIEW_MAX_CHARS : usize = 128 ;
@@ -44,6 +46,7 @@ const JSON_BODY_PREVIEW_MAX_BYTES: usize = 1024;
4446///
4547/// - Snowflake-provided fields: [`snowflake_code`](Error::snowflake_code),
4648/// [`snowflake_message`](Error::snowflake_message), [`query_id`](Error::query_id)
49+ /// - Cancellation failures: [`is_cancelled`](Error::is_cancelled)
4750/// - Decode failures: [`as_schema_error`](Error::as_schema_error) for schema validation failures,
4851/// [`as_cell_decode_error`](Error::as_cell_decode_error) for cell conversion failures,
4952/// [`as_custom_plan_error`](Error::as_custom_plan_error) for plan-time failures raised by a hand-written decoder,
@@ -119,6 +122,8 @@ pub enum ErrorKind {
119122 Network ,
120123 /// Snowflake rejected the request and returned a server-side error message.
121124 Server ,
125+ /// A user-requested cancellation prevented submission or terminated the query.
126+ Cancelled ,
122127 /// The current session token is no longer valid.
123128 SessionExpired ,
124129 /// The connector timed out while waiting for a response.
@@ -157,6 +162,7 @@ impl Error {
157162 Repr :: Auth ( _) => ErrorKind :: Auth ,
158163 Repr :: Network { .. } => ErrorKind :: Network ,
159164 Repr :: Server ( _) => ErrorKind :: Server ,
165+ Repr :: Cancelled ( _) => ErrorKind :: Cancelled ,
160166 Repr :: SessionExpired ( _) => ErrorKind :: SessionExpired ,
161167 Repr :: Timeout { .. } => ErrorKind :: Timeout ,
162168 Repr :: Protocol { .. } => ErrorKind :: Protocol ,
@@ -176,6 +182,7 @@ impl Error {
176182 Repr :: Auth ( AuthError :: LoginRejected { message } ) => message. as_deref ( ) ,
177183 Repr :: SessionExpired ( SessionExpiredError { message, .. } ) => message. as_deref ( ) ,
178184 Repr :: Server ( ServerError { message, .. } ) => message. as_deref ( ) ,
185+ Repr :: Cancelled ( CancelledError { message, .. } ) => message. as_deref ( ) ,
179186 _ => None ,
180187 }
181188 }
@@ -185,6 +192,7 @@ impl Error {
185192 match & * self . repr {
186193 Repr :: SessionExpired ( SessionExpiredError { code, .. } ) => code. as_deref ( ) ,
187194 Repr :: Server ( ServerError { code, .. } ) => code. as_deref ( ) ,
195+ Repr :: Cancelled ( CancelledError { code, .. } ) => code. as_deref ( ) ,
188196 _ => None ,
189197 }
190198 }
@@ -204,6 +212,7 @@ impl Error {
204212 | Repr :: Protocol { query_id, .. }
205213 | Repr :: Internal { query_id, .. } => query_id. as_deref ( ) ,
206214 Repr :: Server ( ServerError { query_id, .. } ) => query_id. as_deref ( ) ,
215+ Repr :: Cancelled ( CancelledError { query_id, .. } ) => query_id. as_deref ( ) ,
207216 Repr :: SessionExpired ( SessionExpiredError { query_id, .. } ) => query_id. as_deref ( ) ,
208217 _ => None ,
209218 }
@@ -221,6 +230,11 @@ impl Error {
221230 matches ! ( & * self . repr, Repr :: Server ( _) )
222231 }
223232
233+ /// Reports whether this error is a cancellation, i.e. [`ErrorKind::Cancelled`].
234+ pub fn is_cancelled ( & self ) -> bool {
235+ matches ! ( & * self . repr, Repr :: Cancelled ( _) )
236+ }
237+
224238 pub fn as_cell_decode_error ( & self ) -> Option < & CellDecodeError > {
225239 match & * self . repr {
226240 Repr :: CellDecode ( error) => Some ( error) ,
@@ -328,6 +342,12 @@ impl From<ServerError> for Error {
328342 }
329343}
330344
345+ impl From < CancelledError > for Error {
346+ fn from ( error : CancelledError ) -> Self {
347+ Self :: new ( Repr :: Cancelled ( error) )
348+ }
349+ }
350+
331351impl From < SessionExpiredError > for Error {
332352 fn from ( error : SessionExpiredError ) -> Self {
333353 Self :: new ( Repr :: SessionExpired ( error) )
@@ -500,6 +520,20 @@ impl ServerError {
500520 }
501521}
502522
523+ impl CancelledError {
524+ pub ( crate ) fn new (
525+ code : Option < String > ,
526+ message : Option < String > ,
527+ query_id : Option < Arc < str > > ,
528+ ) -> Self {
529+ Self {
530+ code : code. map ( String :: into_boxed_str) ,
531+ message : message. map ( String :: into_boxed_str) ,
532+ query_id,
533+ }
534+ }
535+ }
536+
503537impl SessionExpiredError {
504538 pub ( crate ) fn new (
505539 code : Option < String > ,
@@ -527,6 +561,10 @@ impl TimeoutError {
527561 Self :: Query
528562 }
529563
564+ pub ( crate ) fn query_cancel ( ) -> Self {
565+ Self :: QueryCancel
566+ }
567+
530568 #[ cfg( feature = "external-browser-sso" ) ]
531569 pub ( crate ) fn browser_callback ( ) -> Self {
532570 Self :: BrowserCallback
@@ -745,6 +783,37 @@ mod tests {
745783 assert_eq ! ( server_err. to_string( ) , "Snowflake server error 390100" ) ;
746784 }
747785
786+ #[ test]
787+ fn cancelled_error_exposes_structured_details ( ) {
788+ let err: Error = CancelledError :: new (
789+ Some ( "000604" . to_string ( ) ) ,
790+ Some ( "SQL execution canceled" . to_string ( ) ) ,
791+ Some ( Arc :: from ( "query-id" ) ) ,
792+ )
793+ . into ( ) ;
794+
795+ assert_eq ! ( err. kind( ) , ErrorKind :: Cancelled ) ;
796+ assert ! ( err. is_cancelled( ) ) ;
797+ assert_eq ! ( err. snowflake_code( ) , Some ( "000604" ) ) ;
798+ assert_eq ! ( err. snowflake_message( ) , Some ( "SQL execution canceled" ) ) ;
799+ assert_eq ! ( err. query_id( ) , Some ( "query-id" ) ) ;
800+ assert_eq ! (
801+ err. to_string( ) ,
802+ "query cancelled: SQL execution canceled (query id: query-id)"
803+ ) ;
804+ }
805+
806+ #[ test]
807+ fn cancelled_error_without_server_fields_has_stable_display ( ) {
808+ let err: Error = CancelledError :: new ( None , None , None ) . into ( ) ;
809+ assert_eq ! ( err. kind( ) , ErrorKind :: Cancelled ) ;
810+ assert ! ( err. is_cancelled( ) ) ;
811+ assert_eq ! ( err. to_string( ) , "query cancelled" ) ;
812+ assert_eq ! ( err. snowflake_code( ) , None ) ;
813+ assert_eq ! ( err. snowflake_message( ) , None ) ;
814+ assert_eq ! ( err. query_id( ) , None ) ;
815+ }
816+
748817 #[ test]
749818 fn session_expired_preserves_snowflake_fields ( ) {
750819 let err: Error = SessionExpiredError :: new (
0 commit comments