systemprompt_models/managed/
content_identity.rs1use serde::Serialize;
8use std::collections::BTreeMap;
9use systemprompt_identifiers::ResourceRevisionId;
10
11use super::assets::AssetDigest;
12use super::bundle::RevisionBundle;
13use super::error::RevisionBundleError;
14use super::manifest::FileEntry;
15
16type Result<T> = std::result::Result<T, RevisionBundleError>;
17
18#[derive(Serialize)]
19struct ContentNode<'a> {
20 files: &'a BTreeMap<String, FileEntry>,
21 dependencies: BTreeMap<String, AssetDigest>,
22}
23
24impl RevisionBundle {
25 pub fn content_digest(&self) -> Result<AssetDigest> {
26 self.verify()?;
27 self.node_content_digest(&self.root, 0, &mut BTreeMap::new())
28 }
29
30 fn node_content_digest(
31 &self,
32 id: &ResourceRevisionId,
33 depth: usize,
34 memo: &mut BTreeMap<ResourceRevisionId, AssetDigest>,
35 ) -> Result<AssetDigest> {
36 if depth > 64 {
37 return Err(RevisionBundleError::Integrity);
38 }
39 if let Some(digest) = memo.get(id) {
40 return Ok(digest.clone());
41 }
42 let manifest = self
43 .revisions
44 .get(id)
45 .ok_or(RevisionBundleError::Integrity)?;
46 let dependencies = manifest
47 .dependencies
48 .iter()
49 .map(|(key, dependency)| {
50 Ok((
51 key.clone(),
52 self.node_content_digest(&dependency.revision_id, depth + 1, memo)?,
53 ))
54 })
55 .collect::<Result<BTreeMap<_, _>>>()?;
56 let digest = AssetDigest::of(&serde_jcs::to_vec(&ContentNode {
57 files: &manifest.files,
58 dependencies,
59 })?);
60 memo.insert(id.clone(), digest.clone());
61 Ok(digest)
62 }
63}