1use std::collections::BTreeMap;
9use std::io::{self, Read};
10use std::path::PathBuf;
11
12use base64::Engine;
13use serde::{Deserialize, Serialize};
14use sha2::{Digest, Sha256};
15
16pub fn blob_id(bytes: &[u8]) -> String {
18 hex::encode(Sha256::digest(bytes))
19}
20
21pub fn blob_id_of(mut reader: impl Read) -> io::Result<(String, u64)> {
24 let mut hasher = Sha256::new();
25 let size = io::copy(&mut reader, &mut hasher)?;
26 Ok((hex::encode(hasher.finalize()), size))
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(try_from = "BlobSourceWire", into = "BlobSourceWire")]
35pub enum BlobSource {
36 File(PathBuf),
37 Bytes(Vec<u8>),
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub(crate) struct BlobSourceWire {
46 #[serde(default, skip_serializing_if = "Option::is_none")]
47 file: Option<PathBuf>,
48 #[serde(
49 default,
50 skip_serializing_if = "Option::is_none",
51 with = "base64_bytes"
52 )]
53 bytes: Option<Vec<u8>>,
54}
55
56impl TryFrom<BlobSourceWire> for BlobSource {
57 type Error = &'static str;
58
59 fn try_from(raw: BlobSourceWire) -> Result<Self, Self::Error> {
60 match (raw.file, raw.bytes) {
61 (Some(file), None) => Ok(BlobSource::File(file)),
62 (None, Some(bytes)) => Ok(BlobSource::Bytes(bytes)),
63 _ => Err("a blob is kept as `{ file }` or as `{ bytes }`"),
64 }
65 }
66}
67
68impl From<BlobSource> for BlobSourceWire {
69 fn from(source: BlobSource) -> Self {
70 match source {
71 BlobSource::File(file) => BlobSourceWire {
72 file: Some(file),
73 bytes: None,
74 },
75 BlobSource::Bytes(bytes) => BlobSourceWire {
76 file: None,
77 bytes: Some(bytes),
78 },
79 }
80 }
81}
82
83mod base64_bytes {
84 use super::*;
85 use serde::{Deserializer, Serializer};
86
87 const ENGINE: base64::engine::GeneralPurpose = base64::engine::general_purpose::STANDARD;
88
89 pub fn serialize<S: Serializer>(
90 bytes: &Option<Vec<u8>>,
91 serializer: S,
92 ) -> Result<S::Ok, S::Error> {
93 match bytes {
94 Some(bytes) => serializer.serialize_str(&ENGINE.encode(bytes)),
95 None => serializer.serialize_none(),
96 }
97 }
98
99 pub fn deserialize<'de, D: Deserializer<'de>>(
100 deserializer: D,
101 ) -> Result<Option<Vec<u8>>, D::Error> {
102 let text = String::deserialize(deserializer)?;
103 ENGINE
104 .decode(text)
105 .map(Some)
106 .map_err(serde::de::Error::custom)
107 }
108}
109
110pub type Blobs = BTreeMap<String, BlobSource>;