Skip to main content

pagesift_api/types/
attachment.rs

1//! 附件:随页面一起导出的文件(PDF 内嵌图片、Office 媒体、网页图片)。
2//!
3//! 对外的 JSON 只有 `{id, format, bytes, hash}`(加上来源 `source`);文件内容不进 JSON,
4//! HTTP API 用单独的接口下载(`GET /v2/attachments/{id}`)。在 pagesift 内部(缓存、多服务器的
5//! 任务结果)用非人类可读的二进制编码时连同内容一起编码,结果交给网关时附件不丢。
6
7use std::sync::Arc;
8
9use serde::de::{self, Deserializer, MapAccess, Visitor};
10use serde::ser::{SerializeStruct, Serializer};
11use serde::{Deserialize, Serialize};
12
13/// 一个附件。`id` 是内容寻址的文件名:SHA-256 的前 32 位十六进制加扩展名(与 PDF、Office 的
14/// 导出文件名同一规则),同一内容只有一个 id。
15#[derive(Debug, Clone, PartialEq, Eq, Default)]
16pub struct Attachment {
17    pub id: String,
18    /// 格式(文件扩展名):jpg、png、gif、webp、svg、jp2、tif、jb2……
19    pub format: String,
20    /// 字节数。
21    pub bytes: u64,
22    /// 内容的 SHA-256(小写十六进制)。
23    pub hash: String,
24    /// 来源:网页图片的原地址、Office 文档里的部件路径(与 Markdown 里的引用一致);PDF 图片为空
25    /// (Markdown 里直接引用 `images/<id>`)。
26    pub source: Option<String>,
27    /// 文件内容。
28    pub data: Option<Arc<[u8]>>,
29}
30
31impl Attachment {
32    /// 由内容构造:算哈希、定 id。
33    pub fn new(data: Vec<u8>, format: &str, source: Option<String>) -> Self {
34        let hash = sha256_hex(&data);
35        Self {
36            id: format!("{}.{format}", &hash[..32]), // [协议] 与 PDF/Office 导出同一规则:32 位十六进制 = 128 位哈希前缀
37            format: format.to_string(),
38            bytes: data.len() as u64,
39            hash,
40            source,
41            data: Some(data.into()),
42        }
43    }
44
45    /// 已经算好哈希(PDF、Office 的导出)时直接构造。
46    pub fn with_hash(hash: String, format: &str, data: Vec<u8>, source: Option<String>) -> Self {
47        Self {
48            id: format!("{}.{format}", &hash[..hash.len().min(32)]), // [协议] 同上
49            format: format.to_string(),
50            bytes: data.len() as u64,
51            hash,
52            source,
53            data: Some(data.into()),
54        }
55    }
56
57    /// 按格式给的 MIME 类型(下载接口的 Content-Type)。
58    pub fn mime(&self) -> &'static str {
59        mime_of(&self.format)
60    }
61}
62
63/// 扩展名 → MIME 类型(IANA 媒体类型注册表)。
64pub fn mime_of(format: &str) -> &'static str {
65    match format {
66        "jpg" | "jpeg" => "image/jpeg",
67        "png" => "image/png",
68        "gif" => "image/gif",
69        "webp" => "image/webp",
70        "svg" => "image/svg+xml",
71        "bmp" => "image/bmp",
72        "avif" => "image/avif",
73        "ico" => "image/vnd.microsoft.icon",
74        "tif" | "tiff" => "image/tiff",
75        "jp2" => "image/jp2",
76        "j2k" => "image/j2c",
77        "jb2" => "image/x-jbig2",
78        "emf" => "image/emf",
79        "wmf" => "image/wmf",
80        _ => "application/octet-stream",
81    }
82}
83
84fn sha256_hex(data: &[u8]) -> String {
85    let digest = ring::digest::digest(&ring::digest::SHA256, data);
86    digest.as_ref().iter().map(|b| format!("{b:02x}")).collect()
87}
88
89/// 原样写成 serde 的字节串(二进制编码里是一个长度前缀 + 原样字节,不是逐字节的数组)。
90struct Raw<'a>(&'a [u8]);
91
92impl Serialize for Raw<'_> {
93    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
94        s.serialize_bytes(self.0)
95    }
96}
97
98impl Serialize for Attachment {
99    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
100        // 人类可读(JSON)的出口只列元数据;内部二进制编码连内容一起带上
101        let data = self.data.as_deref().filter(|_| !s.is_human_readable());
102        // [协议] 固定字段 id、format、bytes、hash 共 4 个
103        let fields = 4 + usize::from(self.source.is_some()) + usize::from(data.is_some());
104        let mut st = s.serialize_struct("Attachment", fields)?;
105        st.serialize_field("id", &self.id)?;
106        st.serialize_field("format", &self.format)?;
107        st.serialize_field("bytes", &self.bytes)?;
108        st.serialize_field("hash", &self.hash)?;
109        if let Some(src) = &self.source {
110            st.serialize_field("source", src)?;
111        }
112        if let Some(d) = data {
113            st.serialize_field("data", &Raw(d))?;
114        }
115        st.end()
116    }
117}
118
119impl<'de> Deserialize<'de> for Attachment {
120    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
121        d.deserialize_map(AttachmentVisitor)
122    }
123}
124
125struct AttachmentVisitor;
126
127impl<'de> Visitor<'de> for AttachmentVisitor {
128    type Value = Attachment;
129
130    fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
131        f.write_str("an attachment")
132    }
133
134    fn visit_map<A: MapAccess<'de>>(self, mut m: A) -> Result<Attachment, A::Error> {
135        let mut a = Attachment::default();
136        while let Some(key) = m.next_key::<String>()? {
137            match key.as_str() {
138                "id" => a.id = m.next_value()?,
139                "format" => a.format = m.next_value()?,
140                "bytes" => a.bytes = m.next_value()?,
141                "hash" => a.hash = m.next_value()?,
142                "source" => a.source = m.next_value()?,
143                "data" => a.data = Some(m.next_value::<Bytes>()?.0.into()),
144                _ => {
145                    m.next_value::<de::IgnoredAny>()?;
146                }
147            }
148        }
149        if a.id.is_empty() {
150            return Err(de::Error::missing_field("id"));
151        }
152        Ok(a)
153    }
154}
155
156/// 读字节串:二进制编码给 bytes,自描述格式也可能给数组。
157struct Bytes(Vec<u8>);
158
159impl<'de> Deserialize<'de> for Bytes {
160    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
161        struct V;
162        impl<'de> Visitor<'de> for V {
163            type Value = Bytes;
164            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
165                f.write_str("bytes")
166            }
167            fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Bytes, E> {
168                Ok(Bytes(v.to_vec()))
169            }
170            fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Bytes, E> {
171                Ok(Bytes(v))
172            }
173            fn visit_seq<A: de::SeqAccess<'de>>(self, mut s: A) -> Result<Bytes, A::Error> {
174                let mut out = Vec::new();
175                while let Some(b) = s.next_element::<u8>()? {
176                    out.push(b);
177                }
178                Ok(Bytes(out))
179            }
180        }
181        d.deserialize_bytes(V)
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn json_lists_metadata_only() {
191        let a = Attachment::new(
192            b"\x89PNG\r\n\x1a\nxyz".to_vec(),
193            "png",
194            Some("https://e.x/a.png".into()),
195        );
196        assert_eq!(a.id.len(), 32 + 4);
197        assert_eq!(a.bytes, 11);
198        let v = serde_json::to_value(&a).unwrap();
199        assert_eq!(v["format"], "png");
200        assert_eq!(v["hash"].as_str().unwrap().len(), 64);
201        assert!(v.get("data").is_none(), "内容不进 JSON");
202        let back: Attachment = serde_json::from_value(v).unwrap();
203        assert_eq!(back.data, None);
204        assert_eq!(back.id, a.id);
205    }
206}