mzrs-sdk 0.1.12

High-level Rust SDK for Mezon platform
Documentation
//! File attachment source abstraction for the upload API.
//!
//! [`AttachmentSource`] wraps the many ways a caller might provide file data
//! — raw bytes, base64-encoded strings, or a filesystem path — and normalises
//! them all into [`bytes::Bytes`] before the upload step.
//!
//! You never need to construct a variant by name; use the [`From`] impls:
//!
//! ```rust,ignore
//! // raw bytes (Vec<u8>, Bytes, &[u8])
//! client.upload_attachment(png_bytes, "photo.png", "image/png").await?;
//!
//! // base64 string (decoded automatically)
//! client.upload_attachment(b64_string, "photo.png", "image/png").await?;
//!
//! // file path (read asynchronously)
//! client.upload_attachment(Path::new("./output.png"), "output.png", "image/png").await?;
//! ```

use std::path::{Path, PathBuf};

use base64::{engine::general_purpose::STANDARD, Engine};
use bytes::Bytes;

use crate::error::SdkError;

/// The source of data to upload as a file attachment.
///
/// Construct via the [`From`] impls — you do not need to name a variant
/// directly in normal usage.
pub enum AttachmentSource {
    /// Pre-resolved raw bytes — used as-is.
    Bytes(Bytes),
    /// Base64-encoded string — decoded to bytes before upload.
    Base64(String),
    /// Path to a file on disk — read asynchronously before upload.
    Path(PathBuf),
}

impl AttachmentSource {
    /// Resolve the source into raw [`Bytes`].
    ///
    /// # Errors
    ///
    /// - [`SdkError::Base64`] if the string is not valid standard base64.
    /// - [`SdkError::Io`] if the file cannot be read from disk.
    pub async fn into_bytes(self) -> Result<Bytes, SdkError> {
        match self {
            AttachmentSource::Bytes(b) => Ok(b),
            AttachmentSource::Base64(s) => {
                let decoded = STANDARD
                    .decode(s.trim())
                    .map_err(|e| SdkError::Base64(e.to_string()))?;
                Ok(Bytes::from(decoded))
            }
            AttachmentSource::Path(path) => {
                let data = tokio::fs::read(&path).await?;
                Ok(Bytes::from(data))
            }
        }
    }
}

// ── From impls ──────────────────────────────────────────────────────

impl From<Bytes> for AttachmentSource {
    fn from(b: Bytes) -> Self {
        AttachmentSource::Bytes(b)
    }
}

impl From<Vec<u8>> for AttachmentSource {
    fn from(v: Vec<u8>) -> Self {
        AttachmentSource::Bytes(Bytes::from(v))
    }
}

impl From<&[u8]> for AttachmentSource {
    fn from(s: &[u8]) -> Self {
        AttachmentSource::Bytes(Bytes::copy_from_slice(s))
    }
}

/// Interprets the [`String`] as a **base64-encoded** payload.
///
/// If you have a file path as a string, wrap it in [`Path::new`] first.
impl From<String> for AttachmentSource {
    fn from(s: String) -> Self {
        AttachmentSource::Base64(s)
    }
}

/// Interprets the `&str` as a **base64-encoded** payload.
///
/// If you have a file path as a string literal, use `Path::new("...")`.
impl From<&str> for AttachmentSource {
    fn from(s: &str) -> Self {
        AttachmentSource::Base64(s.to_owned())
    }
}

impl From<PathBuf> for AttachmentSource {
    fn from(p: PathBuf) -> Self {
        AttachmentSource::Path(p)
    }
}

impl From<&Path> for AttachmentSource {
    fn from(p: &Path) -> Self {
        AttachmentSource::Path(p.to_owned())
    }
}