1use crate::{
16 BlobStore, ContentHash, CoreError, Identity, Manifest, PutResult, Record, RecordKey,
17 RecordKind, Result, Revision, Store, VectorManifest,
18};
19
20pub async fn undelete<S>(
31 store: &S,
32 key: &RecordKey,
33 now_ms: i64,
34 author: Option<&Identity>,
35) -> Result<Revision>
36where
37 S: Store + BlobStore + ?Sized,
38{
39 let Some(tomb) = store.get_raw(key).await? else {
40 return Err(CoreError::NotFound(key.clone()));
41 };
42 if !tomb.is_tombstone() {
43 return Err(CoreError::Invalid(format!(
44 "{key} is live at revision {}; there is nothing to restore",
45 tomb.revision.counter
46 )));
47 }
48 let Some(kind) = tomb.deleted_kind else {
49 return Err(CoreError::Invalid(format!(
50 "{key}: the deleted body was not retained, so it cannot be restored. \
51 Only manifest kinds retain a body (ADR 0029)."
52 )));
53 };
54
55 let mut missing = Vec::new();
58 for hash in referenced_blobs(kind, &tomb)? {
59 if !store.has_blob(&hash).await? {
60 missing.push(hash.0);
61 }
62 }
63 if !missing.is_empty() {
64 return Err(CoreError::Invalid(format!(
65 "{key}: cannot restore, {} blob(s) the retained body names are gone: {}",
66 missing.len(),
67 missing.join(", ")
68 )));
69 }
70
71 let mut meta = tomb.meta.clone();
72 if let Some(author) = author {
73 meta.author = author.clone();
74 }
75 meta.updated = now_ms;
76
77 let restored = Record {
78 key: key.clone(),
79 kind,
80 revision: Revision {
81 counter: tomb.revision.counter + 1,
82 hash: ContentHash::of(tomb.body.bytes()),
83 },
84 parent: Some(tomb.revision.clone()),
85 body: tomb.body.clone(),
86 meta,
87 links: Vec::new(),
88 ancestors: Vec::new(),
90 deleted_at: None,
91 deleted_blob: None,
92 deleted_kind: None,
93 };
94
95 match store.put_raw(restored, Some(tomb.revision.clone())).await? {
96 PutResult::Committed(rev) => Ok(rev),
97 PutResult::Conflict(c) => Err(CoreError::Invalid(format!(
98 "{key}: the key changed while restoring (now revision {}); nothing was written",
99 c.current.revision.counter
100 ))),
101 }
102}
103
104fn referenced_blobs(kind: RecordKind, tomb: &Record) -> Result<Vec<ContentHash>> {
106 Ok(match kind {
107 RecordKind::VectorManifest => VectorManifest::from_body(&tomb.body)?
108 .entries
109 .into_values()
110 .collect(),
111 RecordKind::GraphManifest => Manifest::from_body(&tomb.body)?
112 .entries
113 .into_values()
114 .collect(),
115 _ => Vec::new(),
119 })
120}