d-engine-client 0.2.4

Client library for interacting with d-engine Raft clusters via gRPC
Documentation
use d_engine_core::client::ErrorCode;
use d_engine_core::client::KvEntry;
use d_engine_proto::client::ClientResponse;
use d_engine_proto::client::client_response::SuccessResult;
use tracing::error;

use crate::ClientApiError;

pub trait ClientResponseExt {
    /// Convert response to boolean write result
    ///
    /// # Returns
    /// - `Ok(true)` on successful write
    /// - `Err` with converted error code on failure
    #[allow(dead_code)]
    fn into_write_result(self) -> std::result::Result<bool, ClientApiError>;

    /// Convert response to read results
    ///
    /// # Returns
    /// Vector of optional key-value pairs wrapped in Result
    fn into_read_results(self) -> std::result::Result<Vec<Option<KvEntry>>, ClientApiError>;

    /// Validate error code in response header
    ///
    /// # Internal Logic
    /// Converts numeric error code to enum variant
    fn validate_error(&self) -> std::result::Result<(), ClientApiError>;

    /// Returns true if this is a successful write with `succeeded = true`.
    #[allow(dead_code)]
    fn is_write_success(&self) -> bool;
}

impl ClientResponseExt for ClientResponse {
    /// Convert response to boolean result
    ///
    /// # Returns
    /// - `Ok(true)` for successful Put/Delete, or successful CAS
    /// - `Ok(false)` for failed CAS
    /// - `Err` with error code on failure
    fn into_write_result(self) -> std::result::Result<bool, ClientApiError> {
        self.validate_error()?;
        match self.success_result {
            Some(SuccessResult::WriteResult(result)) => Ok(result.succeeded),
            other => {
                let found = match &other {
                    Some(SuccessResult::ReadData(_)) => "ReadData",
                    Some(_) => "Unknown",
                    None => "None",
                };
                error!(
                    "Unexpected response type for write operation: expected WriteResult, found {found}"
                );
                Err(ClientApiError::Protocol {
                    code: ErrorCode::InvalidResponse,
                    message: format!(
                        "Unexpected response type: expected WriteResult, found {found}"
                    ),
                    supported_versions: None,
                })
            }
        }
    }

    /// Convert response to read results
    ///
    /// # Returns
    /// Vector of optional key-value pairs wrapped in Result
    fn into_read_results(self) -> std::result::Result<Vec<Option<KvEntry>>, ClientApiError> {
        self.validate_error()?;
        match &self.success_result {
            Some(SuccessResult::ReadData(data)) => data
                .results
                .clone()
                .into_iter()
                .map(|item| {
                    Ok(Some(KvEntry {
                        key: item.key,
                        value: item.value,
                    }))
                })
                .collect(),
            _ => {
                let found = match &self.success_result {
                    Some(SuccessResult::WriteResult(_)) => "WriteResult",
                    None => "None",
                    _ => "Unknown",
                };
                error!(
                    "Unexpected response type for read operation: expected ReadData, found {}",
                    found
                );
                Err(ClientApiError::Protocol {
                    code: ErrorCode::InvalidResponse,
                    message: format!("Unexpected response type: expected ReadData, found {found}",),
                    supported_versions: None,
                })
            }
        }
    }

    /// Validate error code in response header
    ///
    /// # Internal Logic
    /// Converts numeric error code to enum variant
    fn validate_error(&self) -> std::result::Result<(), ClientApiError> {
        match ErrorCode::try_from(self.error).unwrap_or(ErrorCode::Uncategorized) {
            ErrorCode::Success => Ok(()),
            e => Err(e.into()),
        }
    }

    fn is_write_success(&self) -> bool {
        self.error == ErrorCode::Success as i32
            && matches!(&self.success_result, Some(SuccessResult::WriteResult(w)) if w.succeeded)
    }
}