babelforce-manager-sdk 0.42.1

Rust SDK for the babelforce manager APIs — auth, user & agent management, call reporting, metrics, and task automations.
Documentation
use serde_json::Value;

/// The error type returned by every facade call.
#[derive(Debug, thiserror::Error)]
pub enum ManagerError {
    /// A non-2xx response from the manager API.
    #[error("manager API error {status}: {message}")]
    Api {
        /// HTTP status code of the response.
        status: u16,
        /// Machine-readable error code from the response envelope, if present.
        code: Option<String>,
        /// Human-readable message extracted from the response envelope.
        message: String,
        /// Raw response body.
        body: String,
    },
    /// A transport/network failure.
    #[error("network error: {0}")]
    Network(String),
    /// A response that could not be decoded.
    #[error("decode error: {0}")]
    Decode(String),
    /// An argument supplied to the SDK was invalid (e.g. a malformed id).
    #[error("invalid argument: {0}")]
    InvalidArgument(String),
}

impl ManagerError {
    /// Build an [`ManagerError::Api`] from a non-2xx status + raw body, extracting `code`/`message`
    /// from the JSON envelope when present (mirrors the TS/Go facades).
    pub(crate) fn from_response(status: u16, body: String) -> Self {
        let mut code = None;
        let mut message = String::new();
        if let Ok(v) = serde_json::from_str::<Value>(&body) {
            code = v.get("code").and_then(Value::as_str).map(str::to_string);
            for k in ["message", "error", "detail"] {
                if let Some(m) = v.get(k).and_then(Value::as_str) {
                    if !m.is_empty() {
                        message = m.to_string();
                        break;
                    }
                }
            }
        }
        if message.is_empty() {
            message = format!("HTTP {status}");
        }
        ManagerError::Api {
            status,
            code,
            message,
            body,
        }
    }
}

// Each generated module exposes its own (structurally identical) `Error<T>`. The facade needs, per
// module: a `map_*_err` (gen Error -> ManagerError) and a `Transient` impl (retry classification).
// These are written explicitly per module (macros can't extend a `:path` metavar with `::Variant`
// in pattern position). Add the matching pair when a new spec's first domain is wrapped.

pub(crate) fn map_manager_err<T>(e: crate::gen::manager::apis::Error<T>) -> ManagerError {
    use crate::gen::manager::apis::Error;
    match e {
        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
        Error::Serde(err) => ManagerError::Decode(err.to_string()),
        Error::Io(err) => ManagerError::Network(err.to_string()),
    }
}

impl<T> crate::retry::Transient for crate::gen::manager::apis::Error<T> {
    fn transient(&self, idempotent: bool) -> bool {
        use crate::gen::manager::apis::Error;
        match self {
            Error::Reqwest(_) | Error::Io(_) => idempotent,
            Error::ResponseError(rc) => {
                let s = rc.status.as_u16();
                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
            }
            _ => false,
        }
    }
}

// The facade's raw-JSON read path (see `resources/applications.rs`) yields `ManagerError`
// directly — same retry semantics as the generated errors above.
impl crate::retry::Transient for ManagerError {
    fn transient(&self, idempotent: bool) -> bool {
        match self {
            ManagerError::Network(_) => idempotent,
            ManagerError::Api { status, .. } => {
                *status == 429 || (idempotent && matches!(status, 502..=504))
            }
            _ => false,
        }
    }
}

pub(crate) fn map_user_err<T>(e: crate::gen::user::apis::Error<T>) -> ManagerError {
    use crate::gen::user::apis::Error;
    match e {
        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
        Error::Serde(err) => ManagerError::Decode(err.to_string()),
        Error::Io(err) => ManagerError::Network(err.to_string()),
    }
}

impl<T> crate::retry::Transient for crate::gen::user::apis::Error<T> {
    fn transient(&self, idempotent: bool) -> bool {
        use crate::gen::user::apis::Error;
        match self {
            Error::Reqwest(_) | Error::Io(_) => idempotent,
            Error::ResponseError(rc) => {
                let s = rc.status.as_u16();
                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
            }
            _ => false,
        }
    }
}

pub(crate) fn map_task_automation_err<T>(
    e: crate::gen::task_automation::apis::Error<T>,
) -> ManagerError {
    use crate::gen::task_automation::apis::Error;
    match e {
        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
        Error::Serde(err) => ManagerError::Decode(err.to_string()),
        Error::Io(err) => ManagerError::Network(err.to_string()),
    }
}

impl<T> crate::retry::Transient for crate::gen::task_automation::apis::Error<T> {
    fn transient(&self, idempotent: bool) -> bool {
        use crate::gen::task_automation::apis::Error;
        match self {
            Error::Reqwest(_) | Error::Io(_) => idempotent,
            Error::ResponseError(rc) => {
                let s = rc.status.as_u16();
                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
            }
            _ => false,
        }
    }
}

pub(crate) fn map_task_schedule_err<T>(
    e: crate::gen::task_schedule::apis::Error<T>,
) -> ManagerError {
    use crate::gen::task_schedule::apis::Error;
    match e {
        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
        Error::Serde(err) => ManagerError::Decode(err.to_string()),
        Error::Io(err) => ManagerError::Network(err.to_string()),
    }
}

impl<T> crate::retry::Transient for crate::gen::task_schedule::apis::Error<T> {
    fn transient(&self, idempotent: bool) -> bool {
        use crate::gen::task_schedule::apis::Error;
        match self {
            Error::Reqwest(_) | Error::Io(_) => idempotent,
            Error::ResponseError(rc) => {
                let s = rc.status.as_u16();
                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
            }
            _ => false,
        }
    }
}

pub(crate) fn map_auth_err<T>(e: crate::gen::auth::apis::Error<T>) -> ManagerError {
    use crate::gen::auth::apis::Error;
    match e {
        Error::ResponseError(rc) => ManagerError::from_response(rc.status.as_u16(), rc.content),
        Error::Reqwest(err) => ManagerError::Network(err.to_string()),
        Error::Serde(err) => ManagerError::Decode(err.to_string()),
        Error::Io(err) => ManagerError::Network(err.to_string()),
    }
}

impl<T> crate::retry::Transient for crate::gen::auth::apis::Error<T> {
    fn transient(&self, idempotent: bool) -> bool {
        use crate::gen::auth::apis::Error;
        match self {
            Error::Reqwest(_) | Error::Io(_) => idempotent,
            Error::ResponseError(rc) => {
                let s = rc.status.as_u16();
                s == 429 || (idempotent && (s == 502 || s == 503 || s == 504))
            }
            _ => false,
        }
    }
}