use crate::bytes::format as bytes_format;
use crate::typed::{LexiconType, TypedLexicon};
use serde::{Deserialize, Serialize};
pub const BLOB_NSID: &str = "blob";
#[derive(Serialize, Deserialize, PartialEq, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct Blob {
#[serde(rename = "ref")]
pub ref_: Link,
#[serde(rename = "mimeType")]
pub mime_type: String,
pub size: u64,
}
impl LexiconType for Blob {
fn lexicon_type() -> &'static str {
BLOB_NSID
}
}
pub type TypedBlob = TypedLexicon<Blob>;
#[derive(Serialize, Deserialize, PartialEq, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct Link {
#[serde(rename = "$link")]
pub link: String,
}
#[derive(Serialize, Deserialize, PartialEq, Clone)]
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct Bytes {
#[serde(rename = "$bytes", with = "bytes_format")]
pub bytes: Vec<u8>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_typed_blob_serialization() {
let blob = Blob {
ref_: Link {
link: "bafkreitest123".to_string(),
},
mime_type: "image/png".to_string(),
size: 12345,
};
let typed_blob = TypedLexicon::new(blob);
let json = serde_json::to_value(&typed_blob).unwrap();
assert_eq!(json["$type"], "blob");
assert_eq!(json["ref"]["$link"], "bafkreitest123");
assert_eq!(json["mimeType"], "image/png");
assert_eq!(json["size"], 12345);
}
#[test]
fn test_typed_blob_deserialization() {
let json = json!({
"$type": "blob",
"ref": {
"$link": "bafkreideserialized"
},
"mimeType": "video/mp4",
"size": 54321
});
let typed_blob: TypedBlob = serde_json::from_value(json).unwrap();
assert_eq!(typed_blob.inner.ref_.link, "bafkreideserialized");
assert_eq!(typed_blob.inner.mime_type, "video/mp4");
assert_eq!(typed_blob.inner.size, 54321);
assert!(typed_blob.has_type_field());
assert!(typed_blob.validate().is_ok());
}
#[test]
fn test_typed_blob_without_type_field() {
let json = json!({
"ref": {
"$link": "bafkreinotype"
},
"mimeType": "text/plain",
"size": 100
});
let typed_blob: TypedBlob = serde_json::from_value(json).unwrap();
assert_eq!(typed_blob.inner.ref_.link, "bafkreinotype");
assert_eq!(typed_blob.inner.mime_type, "text/plain");
assert_eq!(typed_blob.inner.size, 100);
assert!(!typed_blob.has_type_field());
assert!(typed_blob.validate().is_err());
}
#[test]
fn test_typed_blob_round_trip() {
let original = Blob {
ref_: Link {
link: "bafkreiroundtrip".to_string(),
},
mime_type: "application/octet-stream".to_string(),
size: 999999,
};
let typed = TypedLexicon::new(original.clone());
let json = serde_json::to_string(&typed).unwrap();
let deserialized: TypedBlob = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.inner.ref_.link, original.ref_.link);
assert_eq!(deserialized.inner.mime_type, original.mime_type);
assert_eq!(deserialized.inner.size, original.size);
assert!(deserialized.has_type_field());
}
#[test]
fn test_legacy_blob_with_explicit_type() {
let json_old_format = r#"{
"$type": "blob",
"ref": {
"$link": "bafkreilegacy"
},
"mimeType": "image/gif",
"size": 777
}"#;
let typed_blob: TypedBlob = serde_json::from_str(json_old_format).unwrap();
assert_eq!(typed_blob.inner.ref_.link, "bafkreilegacy");
assert_eq!(typed_blob.inner.mime_type, "image/gif");
assert_eq!(typed_blob.inner.size, 777);
assert!(typed_blob.has_type_field());
let json = serde_json::to_value(&typed_blob).unwrap();
assert_eq!(json["$type"], "blob");
}
#[test]
fn test_blob_in_context() {
#[derive(Debug, Serialize, Deserialize)]
struct TestRecord {
title: String,
thumbnail: TypedBlob,
}
let record = TestRecord {
title: "Test Image".to_string(),
thumbnail: TypedLexicon::new(Blob {
ref_: Link {
link: "bafkreicontext".to_string(),
},
mime_type: "image/jpeg".to_string(),
size: 256000,
}),
};
let json = serde_json::to_value(&record).unwrap();
assert_eq!(json["title"], "Test Image");
assert_eq!(json["thumbnail"]["$type"], "blob");
assert_eq!(json["thumbnail"]["ref"]["$link"], "bafkreicontext");
assert_eq!(json["thumbnail"]["mimeType"], "image/jpeg");
assert_eq!(json["thumbnail"]["size"], 256000);
}
}