git_bug/entities/identity/legacy/
mod.rs1use std::str::FromStr;
15
16use simd_json::{
17 base::ValueTryAsMutObject,
18 derived::{ValueTryAsScalar, ValueTryIntoObject, ValueTryIntoString},
19 owned,
20};
21use url::Url;
22
23use super::Identity;
24use crate::replica::{
25 Replica,
26 entity::{EntityRead, id::entity_id::EntityId, nonce::Nonce},
27};
28
29#[derive(Debug)]
36pub struct VersionEntry {
37 pub version: u64,
39
40 pub times: Vec<(String, u64)>,
44
45 pub unix_time: u64,
47
48 pub metadata: Option<Vec<(String, String)>>,
51
52 pub name: Option<String>,
54
55 pub email: Option<String>,
57
58 pub login: Option<String>,
60
61 pub avatar_url: Option<Url>,
63
64 pub(super) nonce: Nonce,
74}
75
76impl Identity {
77 #[allow(clippy::missing_panics_doc)]
89 #[allow(clippy::too_many_lines)]
90 pub fn read_legacy(
91 replica: &Replica,
92 id: EntityId<Self>,
93 ) -> Result<Vec<VersionEntry>, read::Error> {
94 const VERSION_ENTRY_NAME: &str = "version";
95
96 let root_id = Identity::last_git_commit(replica, id)?;
97 let bfs_order = Identity::breadth_first_search(replica.repo(), root_id)?;
98
99 if bfs_order.is_empty() {
100 return Err(read::Error::Empty);
101 }
102
103 let mut version_entries = vec![];
104 let mut is_first_commit = true;
105 for commit in bfs_order.into_iter().rev() {
106 if !is_first_commit && commit.parent_ids().count() == 0 {
110 return Err(read::Error::MultipleLeafs);
111 }
112
113 {
118 let tree = commit.tree()?;
119 for entry in tree.iter() {
124 let entry = entry.map_err(|err| {
125 #[allow(clippy::unit_cmp)]
128 {
129 assert_eq!(err.inner, ());
132 }
133
134 read::Error::FailedTreeEntryDecode()
135 })?;
136 let mut object = if entry.filename() == VERSION_ENTRY_NAME {
137 entry.object().map_err(|err| read::Error::MissingObject {
138 id: id.as_id(),
139 error: err,
140 })?
141 } else {
142 return Err(read::Error::UnknownEntityName(entry.filename().to_owned()));
143 };
144
145 let entry: VersionEntry = {
146 use crate::replica::entity::operation::operation_data::get;
147
148 let mut value =
149 simd_json::to_owned_value(&mut object.data).map_err(|err| {
150 read::Error::InvalidJsonVersionEntry {
151 got: String::from_utf8_lossy(&(object.data.clone()))
152 .to_string(),
153 error: err,
154 }
155 })?;
156 let object = value.try_as_object_mut()?;
157
158 VersionEntry {
159 version: get! {object, "version", try_as_u64, read::Error},
160 times: get! {
161 @map[preserve-order] object,
162 "times", try_as_u64, read::Error},
163 unix_time: get! {object, "unix_time", try_as_u64, read::Error},
164 metadata: get! {@option[next] object, "metadata", |some: owned::Value| {
165 let object = some.try_into_object()?;
166
167 Ok::<_, read::Error>(
168 Some(get! {@mk_map object, try_into_string, read::Error}))
169 }, read::Error},
170 name: get! {@option object, "name", try_into_string, read::Error},
171 email: get! {@option object, "email", try_into_string, read::Error},
172 login: get! {@option object, "login", try_into_string, read::Error},
173 avatar_url:
174 get! {@option object, "avatar_url", try_into_string, read::Error}
175 .map(|str| Url::from_str(&str))
176 .transpose()?,
177 nonce: Nonce::try_from(
178 get! {object, "nonce", try_into_string, read::Error},
179 )?,
180 }
181 };
182
183 if entry.version != 2 {
184 return Err(read::Error::VersionMismatch {
185 got: entry.version,
186 expected: 2,
187 });
188 }
189 version_entries.push(entry);
190 }
191 }
192
193 is_first_commit = false;
194 }
195 Ok(version_entries)
196 }
197}
198
199#[allow(missing_docs)]
200pub mod read {
201
202 #[derive(Debug, thiserror::Error)]
203 pub enum Error {
205 #[error(transparent)]
206 ReferenceResolve(#[from] crate::replica::entity::find::Error),
207 #[error(transparent)]
208 BreadthFirstSearch(#[from] crate::replica::entity::bfs::Error),
209
210 #[error("This identity has no recorded legacy versions.")]
211 Empty,
212
213 #[error("Multiple leafs in the identity version DAG")]
214 MultipleLeafs,
215
216 #[error(
217 "Expected to find an tree with the legacy identity commit, but found none. Error: {0}"
218 )]
219 MissingTree(#[from] gix::object::commit::Error),
220
221 #[error("Failed to decode an entry from the legacy identity tree to access.")]
222 FailedTreeEntryDecode(),
223
224 #[error("Found unknown entity {0}, while decoding legacy identity tree.")]
225 UnknownEntityName(gix::bstr::BString),
226
227 #[error(
228 "Failed to find the object blob for the legacy identity version tree entry of {id}, \
229 because: {error}"
230 )]
231 MissingObject {
232 id: crate::replica::entity::id::Id,
233 error: gix::objs::find::existing::Error,
234 },
235
236 #[error("Failed to parse the legacy idenity version entry json ({got}), because: {error}")]
237 InvalidJsonVersionEntry {
238 got: String,
239 error: simd_json::Error,
240 },
241 #[error("Failed to get the expected json field: {field}")]
242 MissingJsonField { field: &'static str },
243 #[error("Failed to parse the field '{field}' as correct type: {err}")]
244 WrongJsonType {
245 err: simd_json::TryTypeError,
246 field: &'static str,
247 },
248 #[error("Expected a json object but got something else: {0}")]
249 ExpectedJsonObject(#[from] simd_json::TryTypeError),
250
251 #[error("Failed to decode the base64 nonce: {0}")]
252 NonceParse(#[from] base64::DecodeSliceError),
253 #[error("Failed to parse the avatar url: {0}")]
254 UrlParse(#[from] url::ParseError),
255
256 #[error(
257 "The legacy identity entry json's version did not match. Got {got}, but expected \
258 {expected}"
259 )]
260 VersionMismatch { got: u64, expected: i32 },
261
262 #[error(
263 "The sequnce of operation composing this legacy identity ({id}), is invalid: {error}"
264 )]
265 InvalidOperationSequence {
266 id: crate::replica::entity::id::Id,
267 error: crate::replica::entity::operation::operations::create::Error<
268 crate::entities::identity::Identity,
269 >,
270 },
271 }
272}