sqlx-core-oldapi 0.6.55

Core of SQLx, the rust SQL toolkit. Not intended to be used directly.
Documentation
use crate::error::DatabaseError;
use odbc_api::{
    handles::{slice_to_cow_utf8, Record},
    Error as OdbcApiError,
};
use std::borrow::Cow;
use std::fmt::{Display, Formatter, Result as FmtResult};

#[derive(Debug)]
pub struct OdbcDatabaseError {
    error: OdbcApiError,
    message: String,
    code: Option<String>,
}

impl OdbcDatabaseError {
    fn diagnostic_record(error: &OdbcApiError) -> Option<&Record> {
        match error {
            OdbcApiError::Diagnostics { record, .. } => Some(record),
            OdbcApiError::InvalidRowArraySize { record, .. } => Some(record),
            OdbcApiError::UnsupportedOdbcApiVersion(record) => Some(record),
            OdbcApiError::UnableToRepresentNull(record) => Some(record),
            OdbcApiError::OracleOdbcDriverDoesNotSupport64Bit(record) => Some(record),
            _ => None,
        }
    }

    fn diagnostic_code(record: &Record) -> Option<String> {
        let code = record.state.as_str();

        if code.as_bytes().iter().all(|&byte| byte == 0) {
            None
        } else {
            Some(code.to_owned())
        }
    }
}

impl From<OdbcApiError> for OdbcDatabaseError {
    fn from(error: OdbcApiError) -> Self {
        let record = Self::diagnostic_record(&error);
        let message = record
            .map(|record| slice_to_cow_utf8(&record.message).into_owned())
            .filter(|message| !message.is_empty())
            .unwrap_or_else(|| error.to_string());
        let code = record.and_then(Self::diagnostic_code);

        Self {
            error,
            message,
            code,
        }
    }
}

impl Display for OdbcDatabaseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        Display::fmt(&self.error, f)
    }
}

impl std::error::Error for OdbcDatabaseError {}

impl DatabaseError for OdbcDatabaseError {
    fn message(&self) -> &str {
        &self.message
    }
    fn code(&self) -> Option<Cow<'_, str>> {
        self.code.as_deref().map(Cow::Borrowed)
    }
    fn as_error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
        self
    }
    fn as_error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static) {
        self
    }
    fn into_error(self: Box<Self>) -> Box<dyn std::error::Error + Send + Sync + 'static> {
        self
    }
}

impl From<OdbcApiError> for crate::error::Error {
    fn from(value: OdbcApiError) -> Self {
        crate::error::Error::Database(Box::new(OdbcDatabaseError::from(value)))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::DatabaseError;
    use odbc_api::handles::{Record, SqlChar, State};

    fn sql_chars(text: &str) -> Vec<SqlChar> {
        text.bytes().map(Into::into).collect()
    }

    #[test]
    fn database_error_uses_odbc_diagnostics_for_message_and_code() {
        let error = OdbcDatabaseError::from(OdbcApiError::Diagnostics {
            function: "SQLExecDirect",
            record: Record {
                state: State(*b"HY000"),
                native_error: 1234,
                message: sql_chars("syntax error near FROM"),
            },
        });

        assert_eq!(error.message(), "syntax error near FROM");
        assert_eq!(error.code().as_deref(), Some("HY000"));
    }
}