cortexdb-sdk 0.2.0-beta.2

Rust HTTP client SDK for CortexDB
Documentation
use serde::de::DeserializeOwned;

use super::AsyncCortexDbClient;
use crate::http::append_query_param;
use crate::{ErrorResponse, SdkError, SdkResult};

impl AsyncCortexDbClient {
    pub(crate) async fn get(&self, path: &str) -> SdkResult<serde_json::Value> {
        self.execute(reqwest::Method::GET, path, None).await
    }

    pub(crate) async fn delete(&self, path: &str) -> SdkResult<serde_json::Value> {
        self.execute(reqwest::Method::DELETE, path, None).await
    }

    pub(crate) async fn post(&self, path: &str, body: &str) -> SdkResult<serde_json::Value> {
        self.execute(reqwest::Method::POST, path, Some(body.to_owned()))
            .await
    }

    pub(crate) async fn post_text(&self, path: &str, body: &str) -> SdkResult<String> {
        self.execute_text(reqwest::Method::POST, path, Some(body.to_owned()))
            .await
    }

    async fn execute(
        &self,
        method: reqwest::Method,
        path: &str,
        body: Option<String>,
    ) -> SdkResult<serde_json::Value> {
        let text = self.execute_text(method, path, body).await?;
        serde_json::from_str(&text).map_err(SdkError::Json)
    }

    async fn execute_text(
        &self,
        method: reqwest::Method,
        path: &str,
        body: Option<String>,
    ) -> SdkResult<String> {
        let mut attempt = 0u32;
        loop {
            match self.send_once(method.clone(), path, body.clone()).await {
                Ok(response) => {
                    let status = response.status().as_u16();
                    if response.status().is_success() {
                        return response
                            .text()
                            .await
                            .map_err(|error| SdkError::Transport(error.to_string()));
                    }
                    if self.is_retryable(status) && attempt < self.max_retries {
                        attempt += 1;
                        self.sleep_before_retry(attempt).await;
                        continue;
                    }
                    let body = response
                        .text()
                        .await
                        .map_err(|error| SdkError::Transport(error.to_string()))?;
                    if let Ok(error_response) = serde_json::from_str::<ErrorResponse>(&body) {
                        return Err(SdkError::CortexDb(error_response));
                    }
                    return Err(SdkError::HttpStatus { status, body });
                }
                Err(error) => {
                    if attempt < self.max_retries {
                        attempt += 1;
                        self.sleep_before_retry(attempt).await;
                        continue;
                    }
                    return Err(SdkError::Transport(error.to_string()));
                }
            }
        }
    }

    async fn send_once(
        &self,
        method: reqwest::Method,
        path: &str,
        body: Option<String>,
    ) -> Result<reqwest::Response, reqwest::Error> {
        let mut request = self
            .client
            .request(method, self.url(path))
            .timeout(self.timeout);
        if let Some(token) = &self.token {
            request = request.bearer_auth(token);
        }
        if let Some(body) = body {
            request = request.body(body);
        }
        request.send().await
    }

    async fn sleep_before_retry(&self, attempt: u32) {
        tokio::time::sleep(self.retry_delay * attempt).await;
    }

    fn is_retryable(&self, status: u16) -> bool {
        matches!(status, 500 | 502 | 503 | 504)
    }

    pub(crate) fn url(&self, path: &str) -> String {
        let scoped_path = match self.tenant.as_deref() {
            Some("default") | None => path.to_owned(),
            Some(tenant) => append_query_param(path, "tenant", tenant),
        };
        format!("{}{}", self.base_url, scoped_path)
    }
}

pub(crate) fn decode_value<T: DeserializeOwned>(value: serde_json::Value) -> SdkResult<T> {
    serde_json::from_value(value).map_err(SdkError::Json)
}