use crate::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Binary {
pub content_type: String,
pub source: BinarySource,
pub name: Option<String>,
}
impl Binary {
pub fn new(content_type: impl Into<String>, source: BinarySource, name: Option<String>) -> Self {
Self {
name,
content_type: content_type.into(),
source,
}
}
pub fn from_base64(content_type: impl Into<String>, content: impl Into<Arc<str>>, name: Option<String>) -> Binary {
Binary {
name,
content_type: content_type.into(),
source: BinarySource::Base64(content.into()),
}
}
pub fn from_url(content_type: impl Into<String>, url: impl Into<String>, name: Option<String>) -> Binary {
Binary {
name,
content_type: content_type.into(),
source: BinarySource::Url(url.into()),
}
}
pub fn from_file(file_path: impl AsRef<Path>) -> Result<Binary> {
let file_path = file_path.as_ref();
let content = std::fs::read(file_path)
.map_err(|e| crate::Error::Internal(format!("Failed to read file '{}': {}", file_path.display(), e)))?;
let content_type = mime_guess::from_path(file_path).first_or_octet_stream().to_string();
let b64_content = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &content);
let name = file_path.file_name().and_then(|n| n.to_str()).map(String::from);
Ok(Binary {
name,
content_type,
source: BinarySource::Base64(b64_content.into()),
})
}
}
impl Binary {
pub fn is_image(&self) -> bool {
self.content_type.trim().to_ascii_lowercase().starts_with("image/")
}
pub fn is_audio(&self) -> bool {
self.content_type.trim().to_ascii_lowercase().starts_with("audio/")
}
pub fn is_pdf(&self) -> bool {
self.content_type.trim().eq_ignore_ascii_case("application/pdf")
}
pub fn into_url(self) -> String {
match self.source {
BinarySource::Url(url) => url,
BinarySource::Base64(b64_content) => {
let filename_section = "";
format!("data:{};{filename_section}base64,{b64_content}", self.content_type)
}
}
}
}
impl Binary {
pub fn size(&self) -> usize {
let mut size = self.content_type.len();
size += self.name.as_ref().map(|n| n.len()).unwrap_or_default();
size += match &self.source {
BinarySource::Url(url) => url.len(),
BinarySource::Base64(data) => data.len(),
};
size
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BinarySource {
Url(String),
Base64(Arc<str>),
}
#[allow(unused)]
fn normalize_name(input: &str) -> String {
input
.chars()
.map(|c| {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '_' | '-' | '(' | ')' => c,
_ => '-',
}
})
.collect()
}