use std::fmt::Write as _;
use sqlite_core::Value;
use crate::blob::{identify_media_type, sha256_hex};
use crate::CarvedRecord;
const CONTEXT: &str = "\"@context\":{\
\"kb\":\"http://example.org/kb/\",\
\"uco-core\":\"https://ontology.unifiedcyberontology.org/uco/core/\",\
\"uco-observable\":\"https://ontology.unifiedcyberontology.org/uco/observable/\",\
\"uco-types\":\"https://ontology.unifiedcyberontology.org/uco/types/\",\
\"uco-vocabulary\":\"https://ontology.unifiedcyberontology.org/uco/vocabulary/\",\
\"xsd\":\"http://www.w3.org/2001/XMLSchema#\"}";
#[must_use]
pub fn bundle_for_records(records: &[CarvedRecord]) -> String {
let blobs: Vec<&[u8]> = records
.iter()
.flat_map(|r| r.values.iter())
.filter_map(|v| match v {
Value::Blob(b) => Some(b.as_slice()),
_ => None,
})
.collect();
let mut graph = String::new();
for (i, bytes) in blobs.iter().enumerate() {
if i > 0 {
graph.push(',');
}
let hash = sha256_hex(bytes);
let mime = identify_media_type(bytes)
.map(|m| format!("\"uco-observable:mimeType\":\"{m}\","))
.unwrap_or_default();
let _ = write!(
graph,
"{{\"@id\":\"kb:observable-blob-{i}\",\
\"@type\":\"uco-observable:ObservableObject\",\
\"uco-core:hasFacet\":[{{\"@id\":\"kb:content-data-facet-{i}\",\
\"@type\":\"uco-observable:ContentDataFacet\",\
{mime}\"uco-observable:sizeInBytes\":{size},\
\"uco-observable:hash\":[{{\"@type\":\"uco-types:Hash\",\
\"uco-types:hashMethod\":{{\"@type\":\"uco-vocabulary:HashNameVocab\",\"@value\":\"SHA256\"}},\
\"uco-types:hashValue\":{{\"@type\":\"xsd:hexBinary\",\"@value\":\"{hash}\"}}}}]}}]}}",
size = bytes.len()
);
}
format!("{{{CONTEXT},\"@graph\":[{graph}]}}")
}