1#![forbid(unsafe_code)]
28#![warn(missing_docs)]
29#![warn(rust_2018_idioms)]
30
31pub mod drift;
32pub mod effective_note;
33pub mod error;
34pub mod history;
35pub mod lifecycle;
36pub mod overrides;
37pub mod registry;
38pub mod write;
39
40pub use error::VaultError;
41pub use history::NoteHistoryEntry;
42pub use lifecycle::HISTORY_DIR_PREFIX;
43pub use overrides::NoteMetadataOverride;
44pub use registry::Vault;
45pub use write::WriteResult;
46
47#[async_trait::async_trait]
60pub trait Registry: Send + Sync {
61 async fn tenant_count(&self) -> Result<u32, gradatum_core::error::GradatumError>;
65
66 async fn locus_count(&self) -> Result<u32, gradatum_core::error::GradatumError>;
71
72 async fn ensure_tenant(
76 &self,
77 tenant_id: &str,
78 ) -> Result<(), gradatum_core::error::GradatumError>;
79
80 async fn read_note_by_id(
94 &self,
95 note_id: &str,
96 ) -> Result<gradatum_core::note::Note, gradatum_core::error::GradatumError>;
97
98 async fn history_versions(
103 &self,
104 note_id: &str,
105 ) -> Result<Vec<i64>, gradatum_core::error::GradatumError>;
106
107 async fn history_get(
116 &self,
117 note_id: &str,
118 ts_ms: i64,
119 ) -> Result<gradatum_core::note::Note, gradatum_core::error::GradatumError>;
120
121 async fn history_restore(
131 &self,
132 note_id: &str,
133 ts_ms: i64,
134 ) -> Result<String, gradatum_core::error::GradatumError>;
135
136 async fn history_diff(
144 &self,
145 note_id: &str,
146 a: &str,
147 b: &str,
148 ) -> Result<Vec<String>, gradatum_core::error::GradatumError>;
149
150 async fn update_note_status(
163 &self,
164 note_id: &str,
165 target: gradatum_core::status::NoteStatus,
166 reason: Option<String>,
167 ) -> Result<(), gradatum_core::error::GradatumError>;
168}
169
170#[async_trait::async_trait]
171impl Registry for Vault {
172 async fn tenant_count(&self) -> Result<u32, gradatum_core::error::GradatumError> {
173 self.index.vault_id_count().await
174 }
175
176 async fn locus_count(&self) -> Result<u32, gradatum_core::error::GradatumError> {
177 self.index.locus_count().await
178 }
179
180 async fn ensure_tenant(
181 &self,
182 tenant_id: &str,
183 ) -> Result<(), gradatum_core::error::GradatumError> {
184 self.index.ensure_vault_id(tenant_id).await
185 }
186
187 async fn read_note_by_id(
188 &self,
189 note_id: &str,
190 ) -> Result<gradatum_core::note::Note, gradatum_core::error::GradatumError> {
191 use gradatum_core::error::GradatumError;
192 use ulid::Ulid;
193
194 let ulid = Ulid::from_string(note_id).map_err(|e| {
195 GradatumError::Storage(format!("read_note_by_id : ULID invalide {note_id:?} : {e}"))
196 })?;
197 let id = gradatum_core::identity::NoteId(ulid);
198
199 self.read_note(id).await.map_err(|e| match e {
200 crate::error::VaultError::Core(inner) => inner,
201 crate::error::VaultError::Storage(msg) => GradatumError::Storage(msg),
202 crate::error::VaultError::Markdown(msg) => {
203 GradatumError::Markdown(format!("read_note_by_id : {msg}"))
204 }
205 crate::error::VaultError::Conflict(hash) => GradatumError::Storage(format!(
207 "read_note_by_id : conflit inattendu hash={:?}",
208 hash
209 )),
210 })
211 }
212
213 async fn history_versions(
214 &self,
215 note_id: &str,
216 ) -> Result<Vec<i64>, gradatum_core::error::GradatumError> {
217 use gradatum_core::error::GradatumError;
218 let id = self.parse_note_id(note_id)?;
219 self.history_versions(id)
220 .await
221 .map_err(|e| GradatumError::Storage(format!("history_versions : {e}")))
222 }
223
224 async fn history_get(
225 &self,
226 note_id: &str,
227 ts_ms: i64,
228 ) -> Result<gradatum_core::note::Note, gradatum_core::error::GradatumError> {
229 use gradatum_core::error::GradatumError;
230 let id = self.parse_note_id(note_id)?;
231 self.history_get(id, ts_ms)
232 .await
233 .map_err(|e| GradatumError::Storage(format!("history_get : {e}")))
234 }
235
236 async fn history_restore(
237 &self,
238 note_id: &str,
239 ts_ms: i64,
240 ) -> Result<String, gradatum_core::error::GradatumError> {
241 use gradatum_core::error::GradatumError;
242 let id = self.parse_note_id(note_id)?;
243
244 let snapshot = self
246 .history_get(id, ts_ms)
247 .await
248 .map_err(|e| GradatumError::Storage(format!("history_restore get snapshot: {e}")))?;
249
250 let written = self
251 .write_note_with_id(snapshot.frontmatter, snapshot.body.markdown, id)
252 .await
253 .map_err(|e| GradatumError::Storage(format!("history_restore write: {e}")))?;
254
255 Ok(written.content_hash.hex())
257 }
258
259 async fn history_diff(
260 &self,
261 note_id: &str,
262 a: &str,
263 b: &str,
264 ) -> Result<Vec<String>, gradatum_core::error::GradatumError> {
265 let id = self.parse_note_id(note_id)?;
266
267 let body_a = self.resolve_history_body(id, a).await?;
269 let body_b = self.resolve_history_body(id, b).await?;
270
271 let lines_a: Vec<&str> = body_a.lines().collect();
273 let lines_b: Vec<&str> = body_b.lines().collect();
274 let diff = diff_lines_brut(&lines_a, &lines_b);
275 Ok(diff)
276 }
277
278 async fn update_note_status(
279 &self,
280 note_id: &str,
281 target: gradatum_core::status::NoteStatus,
282 reason: Option<String>,
283 ) -> Result<(), gradatum_core::error::GradatumError> {
284 use gradatum_core::error::GradatumError;
285
286 let id = self.parse_note_id(note_id)?;
287
288 self.update_status(id, target, reason)
289 .await
290 .map_err(|e| match e {
291 crate::error::VaultError::Core(inner) => inner,
292 crate::error::VaultError::Storage(msg) => GradatumError::Storage(msg),
293 crate::error::VaultError::Markdown(msg) => {
294 GradatumError::Markdown(format!("update_note_status : {msg}"))
295 }
296 crate::error::VaultError::Conflict(hash) => GradatumError::Storage(format!(
298 "update_note_status : conflit inattendu hash={:?}",
299 hash
300 )),
301 })
302 }
303}
304
305impl Vault {
306 fn parse_note_id(
308 &self,
309 note_id: &str,
310 ) -> Result<gradatum_core::identity::NoteId, gradatum_core::error::GradatumError> {
311 use gradatum_core::error::GradatumError;
312 use ulid::Ulid;
313 let ulid = Ulid::from_string(note_id)
314 .map_err(|e| GradatumError::Storage(format!("ULID invalide {note_id:?} : {e}")))?;
315 Ok(gradatum_core::identity::NoteId(ulid))
316 }
317
318 async fn resolve_history_body(
320 &self,
321 id: gradatum_core::identity::NoteId,
322 version_selector: &str,
323 ) -> Result<String, gradatum_core::error::GradatumError> {
324 use gradatum_core::error::GradatumError;
325 if version_selector == "current" {
326 let note = self.read_note(id).await.map_err(|e| {
327 GradatumError::Storage(format!("resolve_history_body current: {e}"))
328 })?;
329 Ok(note.body.markdown)
330 } else {
331 let ts_ms = version_selector.parse::<i64>().map_err(|_| {
332 GradatumError::Storage(format!(
333 "sélecteur de version invalide : attendu 'current' ou timestamp ms, reçu {:?}",
334 version_selector
335 ))
336 })?;
337 let snapshot = self.history_get(id, ts_ms).await.map_err(|e| {
338 GradatumError::Storage(format!("resolve_history_body snapshot: {e}"))
339 })?;
340 Ok(snapshot.body.markdown)
341 }
342 }
343}
344
345fn diff_lines_brut(lines_a: &[&str], lines_b: &[&str]) -> Vec<String> {
353 let max_len = lines_a.len().max(lines_b.len());
356 let mut result = Vec::with_capacity(max_len * 2);
357
358 let mut i = 0;
359 let mut j = 0;
360
361 while i < lines_a.len() || j < lines_b.len() {
362 match (lines_a.get(i), lines_b.get(j)) {
363 (Some(la), Some(lb)) => {
364 if la == lb {
365 result.push(format!(" {}", la));
366 i += 1;
367 j += 1;
368 } else {
369 result.push(format!("-{}", la));
370 result.push(format!("+{}", lb));
371 i += 1;
372 j += 1;
373 }
374 }
375 (Some(la), None) => {
376 result.push(format!("-{}", la));
377 i += 1;
378 }
379 (None, Some(lb)) => {
380 result.push(format!("+{}", lb));
381 j += 1;
382 }
383 (None, None) => break,
384 }
385 }
386
387 result
388}
389
390pub const VERSION: &str = env!("CARGO_PKG_VERSION");
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 #[test]
398 fn version_is_set() {
399 assert!(!VERSION.is_empty());
400 }
401}