use std::sync::Arc;
use serde::de::{self, Deserializer, MapAccess, Visitor};
use serde::ser::{SerializeStruct, Serializer};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Attachment {
pub id: String,
pub format: String,
pub bytes: u64,
pub hash: String,
pub source: Option<String>,
pub data: Option<Arc<[u8]>>,
}
impl Attachment {
pub fn new(data: Vec<u8>, format: &str, source: Option<String>) -> Self {
let hash = sha256_hex(&data);
Self {
id: format!("{}.{format}", &hash[..32]), format: format.to_string(),
bytes: data.len() as u64,
hash,
source,
data: Some(data.into()),
}
}
pub fn with_hash(hash: String, format: &str, data: Vec<u8>, source: Option<String>) -> Self {
Self {
id: format!("{}.{format}", &hash[..hash.len().min(32)]), format: format.to_string(),
bytes: data.len() as u64,
hash,
source,
data: Some(data.into()),
}
}
pub fn mime(&self) -> &'static str {
mime_of(&self.format)
}
}
pub fn mime_of(format: &str) -> &'static str {
match format {
"jpg" | "jpeg" => "image/jpeg",
"png" => "image/png",
"gif" => "image/gif",
"webp" => "image/webp",
"svg" => "image/svg+xml",
"bmp" => "image/bmp",
"avif" => "image/avif",
"ico" => "image/vnd.microsoft.icon",
"tif" | "tiff" => "image/tiff",
"jp2" => "image/jp2",
"j2k" => "image/j2c",
"jb2" => "image/x-jbig2",
"emf" => "image/emf",
"wmf" => "image/wmf",
_ => "application/octet-stream",
}
}
fn sha256_hex(data: &[u8]) -> String {
let digest = ring::digest::digest(&ring::digest::SHA256, data);
digest.as_ref().iter().map(|b| format!("{b:02x}")).collect()
}
struct Raw<'a>(&'a [u8]);
impl Serialize for Raw<'_> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_bytes(self.0)
}
}
impl Serialize for Attachment {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let data = self.data.as_deref().filter(|_| !s.is_human_readable());
let fields = 4 + usize::from(self.source.is_some()) + usize::from(data.is_some());
let mut st = s.serialize_struct("Attachment", fields)?;
st.serialize_field("id", &self.id)?;
st.serialize_field("format", &self.format)?;
st.serialize_field("bytes", &self.bytes)?;
st.serialize_field("hash", &self.hash)?;
if let Some(src) = &self.source {
st.serialize_field("source", src)?;
}
if let Some(d) = data {
st.serialize_field("data", &Raw(d))?;
}
st.end()
}
}
impl<'de> Deserialize<'de> for Attachment {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
d.deserialize_map(AttachmentVisitor)
}
}
struct AttachmentVisitor;
impl<'de> Visitor<'de> for AttachmentVisitor {
type Value = Attachment;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("an attachment")
}
fn visit_map<A: MapAccess<'de>>(self, mut m: A) -> Result<Attachment, A::Error> {
let mut a = Attachment::default();
while let Some(key) = m.next_key::<String>()? {
match key.as_str() {
"id" => a.id = m.next_value()?,
"format" => a.format = m.next_value()?,
"bytes" => a.bytes = m.next_value()?,
"hash" => a.hash = m.next_value()?,
"source" => a.source = m.next_value()?,
"data" => a.data = Some(m.next_value::<Bytes>()?.0.into()),
_ => {
m.next_value::<de::IgnoredAny>()?;
}
}
}
if a.id.is_empty() {
return Err(de::Error::missing_field("id"));
}
Ok(a)
}
}
struct Bytes(Vec<u8>);
impl<'de> Deserialize<'de> for Bytes {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct V;
impl<'de> Visitor<'de> for V {
type Value = Bytes;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("bytes")
}
fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Bytes, E> {
Ok(Bytes(v.to_vec()))
}
fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Bytes, E> {
Ok(Bytes(v))
}
fn visit_seq<A: de::SeqAccess<'de>>(self, mut s: A) -> Result<Bytes, A::Error> {
let mut out = Vec::new();
while let Some(b) = s.next_element::<u8>()? {
out.push(b);
}
Ok(Bytes(out))
}
}
d.deserialize_bytes(V)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn json_lists_metadata_only() {
let a = Attachment::new(
b"\x89PNG\r\n\x1a\nxyz".to_vec(),
"png",
Some("https://e.x/a.png".into()),
);
assert_eq!(a.id.len(), 32 + 4);
assert_eq!(a.bytes, 11);
let v = serde_json::to_value(&a).unwrap();
assert_eq!(v["format"], "png");
assert_eq!(v["hash"].as_str().unwrap().len(), 64);
assert!(v.get("data").is_none(), "内容不进 JSON");
let back: Attachment = serde_json::from_value(v).unwrap();
assert_eq!(back.data, None);
assert_eq!(back.id, a.id);
}
}