infobip-sms-sdk 0.1.0

Async Rust SDK for the Infobip SMS API: send messages, manage scheduled bulks, query delivery reports and logs, fetch inbound SMS, and parse webhook payloads.
Documentation
use serde::de::DeserializeOwned;

use crate::error::{ApiError, ApiException, Error};

/// The two error schemas the API uses, depending on which endpoint family
/// you hit:
///
/// - **Rich** (`ApiError`): used by `/sms/3/*` and `/sms/3/messages`.
/// - **Legacy** (`ApiException`): used by `/sms/1/*` and `/sms/1/preview`.
///
/// We dispatch by endpoint family at the call site so we surface the right
/// shape to the user.
#[derive(Debug, Clone, Copy)]
pub(crate) enum ErrorKind {
    Rich,
    Legacy,
}

/// Decode a successful response into `T`, or convert any HTTP error into the
/// appropriate [`Error`] variant.
///
/// `T = ()` is supported via [`Empty`] for endpoints that return no body.
pub(crate) async fn decode_response<T: DeserializeOwned>(
    response: reqwest::Response,
    kind: ErrorKind,
) -> Result<T, Error> {
    let status = response.status();
    if status.is_success() {
        let bytes = response.bytes().await?;
        if bytes.is_empty() {
            // Allow `T = ()` (or any unit-like deserializable type) by
            // attempting to deserialize an empty JSON object.
            return Ok(serde_json::from_str::<T>("null")
                .or_else(|_| serde_json::from_str::<T>("{}"))?);
        }
        return Ok(serde_json::from_slice(&bytes)?);
    }

    let status_code = status.as_u16();
    let body = response.text().await.unwrap_or_default();

    match kind {
        ErrorKind::Rich => {
            if let Ok(api_error) = serde_json::from_str::<ApiError>(&body) {
                return Err(Error::Api {
                    status: status_code,
                    error: Box::new(api_error),
                });
            }
        }
        ErrorKind::Legacy => {
            if let Ok(exception) = serde_json::from_str::<ApiException>(&body) {
                return Err(Error::Exception {
                    status: status_code,
                    exception: Box::new(exception),
                });
            }
        }
    }

    Err(Error::Unexpected {
        status: status_code,
        body,
    })
}