use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "db",
derive(diesel::AsExpression, diesel::FromSqlRow),
diesel(sql_type = diesel::sql_types::Jsonb)
)]
pub struct Blob {
pub provider_id: String,
pub key: String,
pub content_type: String,
pub byte_size: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub etag: Option<String>,
}
impl Blob {
#[must_use]
pub fn new(
provider_id: impl Into<String>,
key: impl Into<String>,
content_type: impl Into<String>,
byte_size: u64,
) -> Self {
Self {
provider_id: provider_id.into(),
key: key.into(),
content_type: content_type.into(),
byte_size,
etag: None,
}
}
#[must_use]
pub fn with_etag(mut self, etag: impl Into<String>) -> Self {
self.etag = Some(etag.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlobMeta {
pub key: String,
pub content_type: String,
pub byte_size: u64,
pub etag: Option<String>,
}
#[cfg(feature = "db")]
mod diesel_impls {
use std::io::Write as _;
use diesel::backend::Backend;
use diesel::deserialize::{self, FromSql};
use diesel::pg::Pg;
use diesel::serialize::{self, IsNull, Output, ToSql};
use diesel::sql_types::Jsonb;
use super::Blob;
impl ToSql<Jsonb, Pg> for Blob {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
out.write_all(&[1])?;
serde_json::to_writer(out, self)
.map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>)?;
Ok(IsNull::No)
}
}
impl FromSql<Jsonb, Pg> for Blob {
fn from_sql(bytes: <Pg as Backend>::RawValue<'_>) -> deserialize::Result<Self> {
let value = <serde_json::Value as FromSql<Jsonb, Pg>>::from_sql(bytes)?;
serde_json::from_value(value).map_err(Into::into)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blob_roundtrips_via_serde_json() {
let blob = Blob::new("local", "avatars/1.png", "image/png", 1234).with_etag("abc");
let json = serde_json::to_string(&blob).unwrap();
let parsed: Blob = serde_json::from_str(&json).unwrap();
assert_eq!(blob, parsed);
}
#[test]
fn blob_drops_etag_when_none() {
let blob = Blob::new("local", "k", "image/png", 1);
let json = serde_json::to_string(&blob).unwrap();
assert!(!json.contains("etag"));
}
}