From 43fec9aa5c1d3e974898a82759957fb79ced43a2 Mon Sep 17 00:00:00 2001 From: Charles Dixon Date: Thu, 30 Jan 2025 20:48:46 +0000 Subject: [PATCH] RSCBC-19: Rework analyticsx error model --- .../src/analyticsx/analytics.rs | 21 +- sdk/couchbase-core/src/analyticsx/error.rs | 522 +++++++++++++++--- .../src/analyticsx/query_respreader.rs | 229 ++++---- .../src/analyticsx/response_json.rs | 4 +- sdk/couchbase-core/src/queryx/error.rs | 40 +- .../src/queryx/query_respreader.rs | 22 +- 6 files changed, 610 insertions(+), 228 deletions(-) diff --git a/sdk/couchbase-core/src/analyticsx/analytics.rs b/sdk/couchbase-core/src/analyticsx/analytics.rs index ed820dfe..4371bf2d 100644 --- a/sdk/couchbase-core/src/analyticsx/analytics.rs +++ b/sdk/couchbase-core/src/analyticsx/analytics.rs @@ -76,12 +76,7 @@ impl Analytics { let on_behalf_of = opts.on_behalf_of; let mut body = serde_json::to_value(opts).map_err(|e| { - Error::new_generic_error( - e.to_string(), - &self.endpoint, - statement, - client_context_id.clone(), - ) + Error::new_encoding_error(format!("failed to encode query options: {}", e)) })?; // Unwrap is fine, we know this is an object. @@ -104,12 +99,7 @@ impl Analytics { } let body = serde_json::to_vec(body_obj).map_err(|e| { - Error::new_generic_error( - e.to_string(), - &self.endpoint, - statement, - client_context_id.clone(), - ) + Error::new_encoding_error(format!("failed to encode query options: {}", e)) })?; let priority = opts.priority.map(|p| p.to_string()); @@ -133,13 +123,12 @@ impl Analytics { ) .await .map_err(|e| { - Error::new_generic_error_with_source( - e.to_string(), + Error::new_http_error( &self.endpoint, - statement, + statement.to_string(), client_context_id.clone(), - Arc::new(e), ) + .with(Arc::new(e)) })?; QueryRespReader::new(res, &self.endpoint, statement, client_context_id.clone()).await diff --git a/sdk/couchbase-core/src/analyticsx/error.rs b/sdk/couchbase-core/src/analyticsx/error.rs index a1b2a400..95d6bb18 100644 --- a/sdk/couchbase-core/src/analyticsx/error.rs +++ b/sdk/couchbase-core/src/analyticsx/error.rs @@ -6,99 +6,471 @@ use std::sync::Arc; pub type Result = std::result::Result; #[derive(Debug, Clone)] -#[non_exhaustive] pub struct Error { - pub kind: Box, - - // TODO: This shouldn't be arc but I'm losing the will to live. - pub source: Option>, - - pub endpoint: String, - pub statement: String, - pub client_context_id: Option, - - pub status_code: Option, + inner: ErrorImpl, } -impl StdError for Error {} - impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if let Some(source) = &self.source { - write!(f, "{}, caused by: {}", self.kind, source) - } else { - write!(f, "{}", self.kind) - } + write!(f, "{}", self.inner.kind) + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.inner + .source + .as_ref() + .map(|cause| &**cause as &(dyn StdError + 'static)) } } impl Error { - pub fn new_generic_error( + pub(crate) fn new_server_error(e: ServerError) -> Error { + Self { + inner: ErrorImpl { + kind: Box::new(ErrorKind::Server(e)), + source: None, + }, + } + } + + pub(crate) fn new_message_error( msg: impl Into, - endpoint: impl Into, - statement: impl Into, - client_context_id: Option, + endpoint: impl Into>, + statement: impl Into>, + client_context_id: impl Into>, ) -> Error { Self { - kind: Box::new(ErrorKind::Generic { msg: msg.into() }), - source: None, - endpoint: endpoint.into(), - statement: statement.into(), - client_context_id, - status_code: None, + inner: ErrorImpl { + kind: Box::new(ErrorKind::Message { + msg: msg.into(), + endpoint: endpoint.into(), + statement: statement.into(), + client_context_id: client_context_id.into(), + }), + source: None, + }, } } - pub fn new_generic_error_with_source( + pub(crate) fn new_encoding_error(msg: impl Into) -> Error { + Self { + inner: ErrorImpl { + kind: Box::new(ErrorKind::Encoding { msg: msg.into() }), + source: None, + }, + } + } + + pub(crate) fn new_invalid_argument_error( msg: impl Into, + arg: impl Into>, + ) -> Self { + Self { + inner: ErrorImpl { + kind: Box::new(ErrorKind::InvalidArgument { + msg: msg.into(), + arg: arg.into(), + }), + source: None, + }, + } + } + + pub(crate) fn new_http_error( endpoint: impl Into, statement: impl Into, - client_context_id: Option, - source: Arc, - ) -> Error { + client_context_id: impl Into>, + ) -> Self { Self { - kind: Box::new(ErrorKind::Generic { msg: msg.into() }), - source: Some(source), - endpoint: endpoint.into(), - statement: statement.into(), - client_context_id, - status_code: None, + inner: ErrorImpl { + kind: Box::new(ErrorKind::Http { + endpoint: endpoint.into(), + statement: statement.into(), + client_context_id: client_context_id.into(), + }), + source: None, + }, } } + + pub(crate) fn new_unsupported_feature_error(feature: impl Into) -> Self { + Self { + inner: ErrorImpl { + kind: Box::new(ErrorKind::UnsupportedFeature { + feature: feature.into(), + }), + source: None, + }, + } + } + + pub fn kind(&self) -> &ErrorKind { + &self.inner.kind + } + + pub(crate) fn with(mut self, source: Source) -> Error { + self.inner.source = Some(source); + self + } +} + +impl Error { + pub fn is_parsing_failure(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::ParsingFailure, + .. + }) + ) + } + + pub fn is_internal_server_error(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::InternalServerError, + .. + }) + ) + } + + pub fn is_authentication_failure(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::AuthenticationFailure, + .. + }) + ) + } + + pub fn is_compilation_failure(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::CompilationFailure, + .. + }) + ) + } + + pub fn is_temporary_failure(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::TemporaryFailure, + .. + }) + ) + } + + pub fn is_index_not_found(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::IndexNotFound, + .. + }) + ) + } + + pub fn is_index_exists(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::IndexExists, + .. + }) + ) + } + + pub fn is_job_queue_full(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::JobQueueFull, + .. + }) + ) + } + + pub fn is_dataset_not_found(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::DatasetNotFound, + .. + }) + ) + } + + pub fn is_dataverse_not_found(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::DataverseNotFound, + .. + }) + ) + } + + pub fn is_dataset_exists(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::DatasetExists, + .. + }) + ) + } + + pub fn is_dataverse_exists(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::DataverseExists, + .. + }) + ) + } + + pub fn is_link_not_found(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::LinkNotFound, + .. + }) + ) + } + + pub fn is_server_invalid_arg(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::ServerInvalidArg, + .. + }) + ) + } + + pub fn is_unknown(&self) -> bool { + matches!( + self.kind(), + ErrorKind::Server(ServerError { + kind: ServerErrorKind::Unknown, + .. + }) + ) + } +} + +type Source = Arc; + +#[derive(Debug, Clone)] +pub struct ErrorImpl { + kind: Box, + source: Option, } #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum ErrorKind { - Server { error_descs: Vec }, - Json { msg: String }, - Generic { msg: String }, - UnsupportedFeature { feature: String }, + Server(ServerError), + #[non_exhaustive] + Http { + endpoint: String, + statement: String, + client_context_id: Option, + }, + #[non_exhaustive] + Message { + msg: String, + endpoint: Option, + statement: Option, + client_context_id: Option, + }, + #[non_exhaustive] + InvalidArgument { + msg: String, + arg: Option, + }, + #[non_exhaustive] + Encoding { + msg: String, + }, + UnsupportedFeature { + feature: String, + }, } impl Display for ErrorKind { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - ErrorKind::Server { error_descs } => { + ErrorKind::Server(e) => write!(f, "{}", e), + ErrorKind::InvalidArgument { msg, arg } => { + let base_msg = format!("invalid argument: {msg}"); + if let Some(arg) = arg { + write!(f, "{base_msg}, arg: {arg}") + } else { + write!(f, "{base_msg}") + } + } + ErrorKind::Encoding { msg } => write!(f, "encoding error: {msg}"), + ErrorKind::Http { + endpoint, + statement, + client_context_id, + } => { write!( f, - "{}", - error_descs - .iter() - .map(|e| e.to_string()) - .collect::>() - .join(", ") - ) + "http error: endpoint: {endpoint}, statement: {statement}" + )?; + if let Some(client_context_id) = client_context_id { + write!(f, ", client context id: {client_context_id}")?; + } + Ok(()) + } + ErrorKind::Message { + msg, + endpoint, + statement, + client_context_id, + } => { + write!(f, "{msg}")?; + if let Some(endpoint) = endpoint { + write!(f, ", endpoint: {endpoint}")?; + } + if let Some(client_context_id) = client_context_id { + write!(f, ", client context id: {client_context_id}")?; + } + if let Some(statement) = statement { + write!(f, ", statement: {statement}")?; + } + Ok(()) } - ErrorKind::Json { msg } => write!(f, "{}", msg), - ErrorKind::Generic { msg } => write!(f, "{}", msg), ErrorKind::UnsupportedFeature { feature } => { - write!(f, "feature unsupported: {}", feature) + write!(f, "unsupported feature: {}", feature) } } } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ServerError { + kind: ServerErrorKind, + + endpoint: String, + status_code: StatusCode, + code: u32, + msg: String, + + statement: Option, + client_context_id: Option, + + all_error_descs: Vec, +} + +impl ServerError { + pub(crate) fn new( + kind: ServerErrorKind, + endpoint: impl Into, + status_code: StatusCode, + code: u32, + msg: impl Into, + ) -> Self { + Self { + kind, + endpoint: endpoint.into(), + status_code, + code, + msg: msg.into(), + statement: None, + client_context_id: None, + all_error_descs: vec![], + } + } + + pub fn kind(&self) -> &ServerErrorKind { + &self.kind + } + + pub fn endpoint(&self) -> &str { + &self.endpoint + } + + pub fn statement(&self) -> Option<&str> { + self.statement.as_deref() + } + + pub fn status_code(&self) -> StatusCode { + self.status_code + } + + pub fn client_context_id(&self) -> Option<&str> { + self.client_context_id.as_deref() + } + + pub fn code(&self) -> u32 { + self.code + } + + pub fn msg(&self) -> &str { + &self.msg + } + + pub fn all_error_descs(&self) -> &[ErrorDesc] { + &self.all_error_descs + } + + pub(crate) fn with_statement(mut self, statement: impl Into) -> Self { + self.statement = Some(statement.into()); + self + } + + pub(crate) fn with_client_context_id(mut self, client_context_id: impl Into) -> Self { + self.client_context_id = Some(client_context_id.into()); + self + } + + pub(crate) fn with_error_descs(mut self, error_descs: Vec) -> Self { + self.all_error_descs = error_descs; + self + } +} + +impl Display for ServerError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "server error of kind: {} code: {}, msg: {}", + self.kind, self.code, self.msg + )?; + + if let Some(client_context_id) = &self.client_context_id { + write!(f, ", client context id: {}", client_context_id)?; + } + if let Some(statement) = &self.statement { + write!(f, ", statement: {}", statement)?; + } + + write!( + f, + ", endpoint: {}, status code: {}", + self.endpoint, self.status_code + )?; + + if !self.all_error_descs.is_empty() { + write!(f, ", all error descriptions: {:?}", self.all_error_descs)?; + } + + Ok(()) + } +} + #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum ServerErrorKind { @@ -116,10 +488,7 @@ pub enum ServerErrorKind { DataverseExists, LinkNotFound, ServerInvalidArg, - #[non_exhaustive] - Unknown { - msg: String, - }, + Unknown, } impl Display for ServerErrorKind { @@ -139,7 +508,7 @@ impl Display for ServerErrorKind { ServerErrorKind::DataverseExists => write!(f, "analytics scope already exists"), ServerErrorKind::LinkNotFound => write!(f, "link not found"), ServerErrorKind::ServerInvalidArg => write!(f, "invalid argument"), - ServerErrorKind::Unknown { msg } => write!(f, "unknown error: {}", msg), + ServerErrorKind::Unknown => write!(f, "unknown error"), } } } @@ -147,31 +516,40 @@ impl Display for ServerErrorKind { #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub struct ErrorDesc { - pub kind: Box, + kind: ServerErrorKind, - pub code: Option, - pub message: Option, + code: u32, + message: String, } impl ErrorDesc { - pub fn new(kind: ServerErrorKind, code: Option, message: Option) -> Self { + pub fn new(kind: ServerErrorKind, code: u32, message: impl Into) -> Self { Self { - kind: Box::new(kind), + kind, code, - message, + message: message.into(), } } + + pub fn kind(&self) -> &ServerErrorKind { + &self.kind + } + + pub fn code(&self) -> u32 { + self.code + } + + pub fn message(&self) -> &str { + &self.message + } } impl Display for ErrorDesc { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut base = format!("{}", self.kind); - if let Some(code) = self.code { - base.push_str(&format!(" (code: {})", code)); - } - if let Some(msg) = &self.message { - base.push_str(&format!(" - {}", msg)); - } - write!(f, "{base}") + write!( + f, + "error description of kind: {}, code: {}, message: {}", + self.kind, self.code, self.message + ) } } diff --git a/sdk/couchbase-core/src/analyticsx/query_respreader.rs b/sdk/couchbase-core/src/analyticsx/query_respreader.rs index c156f6de..d1efde3c 100644 --- a/sdk/couchbase-core/src/analyticsx/query_respreader.rs +++ b/sdk/couchbase-core/src/analyticsx/query_respreader.rs @@ -1,5 +1,5 @@ use crate::analyticsx::error; -use crate::analyticsx::error::{Error, ErrorDesc, ErrorKind, ServerErrorKind}; +use crate::analyticsx::error::{Error, ErrorDesc, ErrorKind, ServerError, ServerErrorKind}; use crate::analyticsx::query_result::MetaData; use crate::analyticsx::response_json::{QueryError, QueryErrorResponse, QueryMetaData}; use crate::httpx::json_row_stream::JsonRowStream; @@ -36,7 +36,7 @@ pub struct QueryRespReader { streamer: Option, meta_data: Option, - meta_data_error: Option, + meta_data_error: Option, } impl Stream for QueryRespReader { @@ -52,12 +52,12 @@ impl Stream for QueryRespReader { match streamer.poll_next_unpin(cx) { Poll::Ready(Some(Ok(row_data))) => Poll::Ready(Some(Ok(Bytes::from(row_data)))), - Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(error::Error::new_generic_error( - e.to_string(), + Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(Error::new_http_error( &this.endpoint, &this.statement, this.client_context_id.clone(), - )))), + ) + .with(Arc::new(e))))), Poll::Ready(None) => { this.read_final_metadata(); Poll::Ready(None) @@ -74,55 +74,47 @@ impl QueryRespReader { statement: impl Into, client_context_id: Option, ) -> error::Result { + let endpoint = endpoint.into(); + let statement = statement.into(); + let status_code = resp.status(); if status_code != 200 { let body = match resp.bytes().await { Ok(b) => b, Err(e) => { debug!("Failed to read response body on error {}", e); - return Err(error::Error { - kind: Box::new(ErrorKind::Generic { msg: e.to_string() }), - source: Some(Arc::new(e)), - endpoint: endpoint.into(), - status_code: Some(status_code), - statement: statement.into(), - client_context_id, - }); + return Err( + Error::new_http_error(endpoint, statement, client_context_id) + .with(Arc::new(e)), + ); } }; let errors: QueryErrorResponse = match serde_json::from_slice(&body) { Ok(e) => e, Err(e) => { - return Err(error::Error { - kind: Box::new(ErrorKind::Generic { msg: format!( + return Err(Error::new_message_error( + format!( "non-200 status code received {} but parsing error response body failed {}", - status_code, - e - ) }), - source: Some(Arc::new(e)), - endpoint: endpoint.into(), - status_code: Some(status_code), - statement: statement.into(), + status_code, e + ), + endpoint, + statement, client_context_id, - }); + )); } }; if errors.errors.is_empty() { - return Err(error::Error { - kind: Box::new(ErrorKind::Generic { - msg: format!( - "Non-200 status code received {} but response body contained no errors", - status_code - ), - }), - source: None, - endpoint: endpoint.into(), - status_code: Some(status_code), - statement: statement.into(), + return Err(Error::new_message_error( + format!( + "non-200 status code received {} but no error message in response body", + status_code + ), + endpoint, + statement, client_context_id, - }); + )); } return Err(Self::parse_errors( @@ -140,21 +132,17 @@ impl QueryRespReader { match streamer.read_prelude().await { Ok(_) => {} Err(e) => { - return Err(Error::new_generic_error_with_source( - e.to_string(), - endpoint.into(), - statement.into(), - client_context_id, - Arc::new(e), - )); + return Err( + Error::new_http_error(endpoint, statement, client_context_id).with(Arc::new(e)), + ); } }; let has_more_rows = streamer.has_more_rows(); let mut reader = Self { - endpoint: endpoint.into(), - statement: statement.into(), + endpoint, + statement, client_context_id, status_code, streamer: Some(streamer), @@ -181,16 +169,12 @@ impl QueryRespReader { return Ok(meta); } - Err(error::Error { - kind: Box::new(ErrorKind::Generic { - msg: "cannot read meta-data until after all rows are read".to_string(), - }), - source: None, - endpoint: self.endpoint.clone(), - status_code: Some(self.status_code), - statement: self.statement.clone(), - client_context_id: self.client_context_id.clone(), - }) + Err(Error::new_message_error( + "cannot read meta-data until after all rows are read", + self.endpoint.clone(), + self.statement.clone(), + self.client_context_id.clone(), + )) } fn read_final_metadata(&mut self) { @@ -201,14 +185,14 @@ impl QueryRespReader { let epilog = match streamer.epilog() { Ok(e) => e, Err(e) => { - self.meta_data_error = Some(error::Error { - kind: Box::new(ErrorKind::Generic { msg: e.to_string() }), - source: Some(Arc::new(e)), - endpoint: self.endpoint.clone(), - status_code: Some(self.status_code), - statement: self.statement.clone(), - client_context_id: self.client_context_id.clone(), - }); + self.meta_data_error = Some( + Error::new_http_error( + self.endpoint.clone(), + self.statement.clone(), + self.client_context_id.clone(), + ) + .with(Arc::new(e)), + ); return; } }; @@ -216,14 +200,12 @@ impl QueryRespReader { let metadata: QueryMetaData = match serde_json::from_slice(&epilog) { Ok(m) => m, Err(e) => { - self.meta_data_error = Some(error::Error { - kind: Box::new(ErrorKind::Generic { msg: e.to_string() }), - source: Some(Arc::new(e)), - endpoint: self.endpoint.clone(), - status_code: Some(self.status_code), - statement: self.statement.clone(), - client_context_id: self.client_context_id.clone(), - }); + self.meta_data_error = Some(Error::new_message_error( + format!("failed to parse query metadata from epilog: {}", e), + self.endpoint.clone(), + self.statement.clone(), + self.client_context_id.clone(), + )); return; } }; @@ -260,69 +242,70 @@ impl QueryRespReader { statement: impl Into, client_context_id: Option, status_code: StatusCode, - ) -> error::Error { + ) -> Error { let error_descs: Vec = errors .iter() - .map(|error| ErrorDesc { - kind: Box::new(Self::parse_error_kind(error)), - code: error.code, - message: error.msg.clone(), + .map(|error| { + ErrorDesc::new(Self::parse_error_kind(error), error.code, error.msg.clone()) }) .collect(); - error::Error { - kind: Box::new(ErrorKind::Server { error_descs }), - source: None, - endpoint: endpoint.into(), - statement: statement.into(), - client_context_id, - status_code: Some(status_code), + let chosen_desc = &error_descs[0]; + + let mut server_error = ServerError::new( + chosen_desc.kind().clone(), + endpoint, + status_code, + chosen_desc.code(), + chosen_desc.message(), + ) + .with_statement(statement); + + if let Some(client_context_id) = client_context_id { + server_error = server_error.with_client_context_id(client_context_id); + } + + if error_descs.len() > 1 { + server_error = server_error.with_error_descs(error_descs); } + + Error::new_server_error(server_error) } fn parse_error_kind(error: &QueryError) -> ServerErrorKind { - if let Some(err_code) = error.code { - let err_code_group = err_code / 1000; - - let kind = if err_code_group == 20 { - ServerErrorKind::AuthenticationFailure - } else if err_code_group == 24 { - if err_code == 24000 { - ServerErrorKind::ParsingFailure - } else if err_code == 24006 { - ServerErrorKind::LinkNotFound - } else if err_code == 24025 || err_code == 24044 || err_code == 24045 { - ServerErrorKind::DatasetNotFound - } else if err_code == 24034 { - ServerErrorKind::DataverseNotFound - } else if err_code == 24039 { - ServerErrorKind::DataverseExists - } else if err_code == 24040 { - ServerErrorKind::DatasetExists - } else if err_code == 24047 { - ServerErrorKind::IndexNotFound - } else if err_code == 24048 { - ServerErrorKind::IndexExists - } else { - ServerErrorKind::CompilationFailure - } - } else if err_code_group == 25 { - ServerErrorKind::CompilationFailure - } else if err_code == 23000 || err_code == 23003 { - ServerErrorKind::TemporaryFailure - } else if err_code == 23007 { - ServerErrorKind::JobQueueFull + let err_code = error.code; + let err_code_group = err_code / 1000; + + if err_code_group == 20 { + ServerErrorKind::AuthenticationFailure + } else if err_code_group == 24 { + if err_code == 24000 { + ServerErrorKind::ParsingFailure + } else if err_code == 24006 { + ServerErrorKind::LinkNotFound + } else if err_code == 24025 || err_code == 24044 || err_code == 24045 { + ServerErrorKind::DatasetNotFound + } else if err_code == 24034 { + ServerErrorKind::DataverseNotFound + } else if err_code == 24039 { + ServerErrorKind::DataverseExists + } else if err_code == 24040 { + ServerErrorKind::DatasetExists + } else if err_code == 24047 { + ServerErrorKind::IndexNotFound + } else if err_code == 24048 { + ServerErrorKind::IndexExists } else { - ServerErrorKind::Unknown { - msg: format!("unknown error code {}", err_code), - } - }; - - return kind; - } - - ServerErrorKind::Unknown { - msg: "no error code".to_string(), + ServerErrorKind::CompilationFailure + } + } else if err_code_group == 25 { + ServerErrorKind::CompilationFailure + } else if err_code == 23000 || err_code == 23003 { + ServerErrorKind::TemporaryFailure + } else if err_code == 23007 { + ServerErrorKind::JobQueueFull + } else { + ServerErrorKind::Unknown } } } diff --git a/sdk/couchbase-core/src/analyticsx/response_json.rs b/sdk/couchbase-core/src/analyticsx/response_json.rs index 3723d209..6c9cd2ce 100644 --- a/sdk/couchbase-core/src/analyticsx/response_json.rs +++ b/sdk/couchbase-core/src/analyticsx/response_json.rs @@ -28,8 +28,8 @@ impl From for query_result::Warning { #[derive(Debug, Deserialize)] pub struct QueryError { - pub code: Option, - pub msg: Option, + pub code: u32, + pub msg: String, } #[derive(Debug, Deserialize)] diff --git a/sdk/couchbase-core/src/queryx/error.rs b/sdk/couchbase-core/src/queryx/error.rs index 48a2d31e..a5904dd2 100644 --- a/sdk/couchbase-core/src/queryx/error.rs +++ b/sdk/couchbase-core/src/queryx/error.rs @@ -529,12 +529,12 @@ impl StdError for ResourceError {} #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub struct ErrorDesc { - pub kind: ServerErrorKind, + kind: ServerErrorKind, - pub code: u32, - pub message: String, - pub retry: bool, - pub reason: HashMap, + code: u32, + message: String, + retry: bool, + reason: HashMap, } impl ErrorDesc { @@ -553,6 +553,36 @@ impl ErrorDesc { reason, } } + + pub fn kind(&self) -> &ServerErrorKind { + &self.kind + } + + pub fn code(&self) -> u32 { + self.code + } + + pub fn message(&self) -> &str { + &self.message + } + + pub fn retry(&self) -> bool { + self.retry + } + + pub fn reason(&self) -> &HashMap { + &self.reason + } +} + +impl Display for ErrorDesc { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "error description of kind: {}, code: {}, message: {}, retry: {}, reason: {:?}", + self.kind, self.code, self.message, self.retry, self.reason + ) + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/sdk/couchbase-core/src/queryx/query_respreader.rs b/sdk/couchbase-core/src/queryx/query_respreader.rs index 02f437ad..776ec54d 100644 --- a/sdk/couchbase-core/src/queryx/query_respreader.rs +++ b/sdk/couchbase-core/src/queryx/query_respreader.rs @@ -328,26 +328,28 @@ impl QueryRespReader { ) -> Error { let error_descs: Vec = errors .iter() - .map(|error| ErrorDesc { - kind: Self::parse_error_kind(error), - code: error.code, - message: error.msg.clone(), - retry: error.retry.unwrap_or_default(), - reason: error.reason.clone(), + .map(|error| { + ErrorDesc::new( + Self::parse_error_kind(error), + error.code, + error.msg.clone(), + error.retry.unwrap_or_default(), + error.reason.clone(), + ) }) .collect(); let chosen_desc = error_descs .iter() - .find(|desc| !desc.retry) + .find(|desc| !desc.retry()) .unwrap_or(&error_descs[0]); let mut server_error = ServerError::new( - chosen_desc.kind.clone(), + chosen_desc.kind().clone(), endpoint, status_code, - chosen_desc.code, - chosen_desc.message.clone(), + chosen_desc.code(), + chosen_desc.message(), ) .with_client_context_id(client_context_id) .with_statement(statement);