use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::Arc;
use gradatum_core::error::GradatumError;
use gradatum_core::identity::{NoteId, NoteVersion};
use gradatum_core::note::EffectiveNote;
use gradatum_core::scope::OverrideScope;
use gradatum_storage::Storage as _;
use crate::registry::Vault;
impl Vault {
pub async fn get_effective_note(
&self,
id: NoteId,
scope: &OverrideScope,
) -> Result<Arc<EffectiveNote>, GradatumError> {
let scope_key = scope_to_hash(scope);
let key = (id, scope_key);
let index = Arc::clone(&self.index);
let cached = self
.cache
.get(key, move |note_id| async move {
index
.get_content_hash(note_id)
.await?
.ok_or(GradatumError::NoteNotFound(note_id))
})
.await?;
if let Some(arc) = cached {
return Ok(arc);
}
let vault_id = self.tenant_id.as_str();
let id_str = id.to_string();
let record = self
.index
.get_note(vault_id, &id_str)
.await?
.ok_or(GradatumError::NoteNotFound(id))?;
let path_no_locus = format!("{}/{}.md", vault_id, id_str);
let path_with_section = format!("{}/{}/{}.md", vault_id, record.section, id_str);
let md_bytes = if self.storage.exists(&path_no_locus).await.unwrap_or(false) {
self.storage
.read(&path_no_locus)
.await
.map_err(|e| GradatumError::Storage(format!("read .md {path_no_locus}: {e}")))?
} else {
self.storage
.read(&path_with_section)
.await
.map_err(|e| GradatumError::Storage(format!("read .md {path_with_section}: {e}")))?
};
let md_str = String::from_utf8(md_bytes)
.map_err(|e| GradatumError::Storage(format!("UTF-8 decode {id_str}: {e}")))?;
let parsed = gradatum_markdown::parse(&md_str)
.map_err(|e| GradatumError::Storage(format!("parse .md {id_str}: {e}")))?;
let effective = Arc::new(EffectiveNote {
id,
frontmatter: parsed.frontmatter,
body: parsed.body,
version: NoteVersion::initial(),
content_hash: parsed.content_hash,
});
let content_hash = effective.content_hash;
self.cache
.insert(key, Arc::clone(&effective), content_hash)
.await;
Ok(effective)
}
}
fn scope_to_hash(scope: &OverrideScope) -> u64 {
let repr = match scope {
OverrideScope::Vault(v) => format!("vault:{}", v.as_str()),
OverrideScope::Locus(l) => format!("locus:{}", l.as_str()),
OverrideScope::Bearer(b) => format!("bearer:{}", b.as_str()),
};
let mut hasher = DefaultHasher::new();
repr.hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use gradatum_core::scope::{LocusId, VaultId};
#[test]
fn scope_hash_vault_is_stable_in_process() {
let s1 = scope_to_hash(&OverrideScope::Vault(VaultId::new("main")));
let s2 = scope_to_hash(&OverrideScope::Vault(VaultId::new("main")));
assert_eq!(
s1, s2,
"hash doit être stable pour la même valeur dans le même process"
);
}
#[test]
fn scope_hash_vault_vs_locus_differ() {
let vault = scope_to_hash(&OverrideScope::Vault(VaultId::new("main")));
let locus = scope_to_hash(&OverrideScope::Locus(LocusId::new("main")));
assert_ne!(
vault, locus,
"vault et locus avec même id doivent avoir des hashes distincts"
);
}
}