use std::collections::BTreeMap;
use std::io::{self, Read};
use std::path::PathBuf;
use base64::Engine;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
pub fn blob_id(bytes: &[u8]) -> String {
hex::encode(Sha256::digest(bytes))
}
pub fn blob_id_of(mut reader: impl Read) -> io::Result<(String, u64)> {
let mut hasher = Sha256::new();
let size = io::copy(&mut reader, &mut hasher)?;
Ok((hex::encode(hasher.finalize()), size))
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "BlobSourceWire", into = "BlobSourceWire")]
pub enum BlobSource {
File(PathBuf),
Bytes(Vec<u8>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct BlobSourceWire {
#[serde(default, skip_serializing_if = "Option::is_none")]
file: Option<PathBuf>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "base64_bytes"
)]
bytes: Option<Vec<u8>>,
}
impl TryFrom<BlobSourceWire> for BlobSource {
type Error = &'static str;
fn try_from(raw: BlobSourceWire) -> Result<Self, Self::Error> {
match (raw.file, raw.bytes) {
(Some(file), None) => Ok(BlobSource::File(file)),
(None, Some(bytes)) => Ok(BlobSource::Bytes(bytes)),
_ => Err("a blob is kept as `{ file }` or as `{ bytes }`"),
}
}
}
impl From<BlobSource> for BlobSourceWire {
fn from(source: BlobSource) -> Self {
match source {
BlobSource::File(file) => BlobSourceWire {
file: Some(file),
bytes: None,
},
BlobSource::Bytes(bytes) => BlobSourceWire {
file: None,
bytes: Some(bytes),
},
}
}
}
mod base64_bytes {
use super::*;
use serde::{Deserializer, Serializer};
const ENGINE: base64::engine::GeneralPurpose = base64::engine::general_purpose::STANDARD;
pub fn serialize<S: Serializer>(
bytes: &Option<Vec<u8>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match bytes {
Some(bytes) => serializer.serialize_str(&ENGINE.encode(bytes)),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<Vec<u8>>, D::Error> {
let text = String::deserialize(deserializer)?;
ENGINE
.decode(text)
.map(Some)
.map_err(serde::de::Error::custom)
}
}
pub type Blobs = BTreeMap<String, BlobSource>;