git_bug/entities/identity/
mod.rs1use std::{iter, mem};
24
25use log::warn;
26use serde::{Deserialize, Serialize};
27use simd_json::{json_typed, owned};
28
29use self::{
30 identity_operation::{IdentityOperationData, identity_operation_type::IdentityOperationType},
31 snapshot::{history_step::IdentityHistoryStep, timeline::IdentityTimeline},
32};
33use crate::replica::{
34 Replica,
35 cache::impl_cache,
36 entity::{
37 Entity, EntityRead,
38 id::entity_id::EntityId,
39 identity::IdentityStub,
40 lamport,
41 operation::{Operation, operations::Operations},
42 },
43};
44
45pub mod identity_operation;
46pub mod legacy;
47pub mod snapshot;
48
49#[derive(Debug, Deserialize, Serialize)]
52pub struct Identity {
53 id: EntityId<Identity>,
54 operations: Operations<Self>,
55 create_time: lamport::Time,
56 edit_time: lamport::Time,
57 current_head: gix::ObjectId,
58}
59
60impl Entity for Identity {
61 type HistoryStep = IdentityHistoryStep;
62 type OperationData = IdentityOperationData;
63 type Timeline = IdentityTimeline;
64
65 const FORMAT_VERSION: usize = 4;
66 const NAMESPACE: &str = "identities";
67 const TYPENAME: &str = "Identity";
68
69 fn operations(&self) -> &Operations<Self>
70 where
71 Self: Sized,
72 {
73 &self.operations
74 }
75
76 unsafe fn from_parts(
77 operations: Operations<Self>,
78 create_time: lamport::Time,
79 edit_time: lamport::Time,
80 current_head: gix::ObjectId,
81 ) -> Self
82 where
83 Self: Sized,
84 {
85 warn!("Constructing an Identity with a invalid id.");
86 Self {
87 id: operations.root().id(),
90 operations,
91 create_time,
92 edit_time,
93 current_head,
94 }
95 }
96
97 fn create_time(&self) -> &lamport::Time
98 where
99 Self: Sized,
100 {
101 &self.create_time
102 }
103
104 fn edit_time(&self) -> &lamport::Time
105 where
106 Self: Sized,
107 {
108 &self.edit_time
109 }
110
111 fn current_head(&self) -> &gix::oid
112 where
113 Self: Sized,
114 {
115 &self.current_head
116 }
117
118 fn id(&self) -> EntityId<Self>
119 where
120 Self: Sized,
121 {
122 self.id
123 }
124}
125
126#[allow(missing_docs)]
127pub mod read {
128 use super::legacy;
129
130 #[derive(Debug, thiserror::Error)]
131 pub enum Error {
134 #[error("Reading the legacy data failed: {0}")]
135 LegacyRead(#[from] legacy::read::Error),
136
137 #[error("This identity does not contain legacy version entries")]
138 EmptyIdentity,
139
140 #[error("This identity contained a last version entry, with a missing clock name: {0}")]
141 MissingClock(String),
142 }
143}
144
145impl From<read::Error> for crate::replica::entity::read::Error<Identity> {
146 fn from(value: read::Error) -> Self {
147 Self::CustomRead(value)
148 }
149}
150
151impl EntityRead for Identity {
152 type CustomReadError = read::Error;
153
154 #[allow(clippy::too_many_lines)]
155 fn read(
156 replica: &Replica,
157 id: EntityId<Self>,
158 ) -> Result<Self, crate::replica::entity::read::Error<Self>>
159 where
160 Self: Sized,
161 {
162 impl_cache!(@mk_table "identities");
163
164 let last_id = Identity::last_git_commit(replica, id)?;
165
166 let mut key = id.as_id().as_slice().to_owned();
167 key.extend(last_id.as_slice());
168 impl_cache! {@lookup replica.db(), key.as_slice()}
169
170 let mut version_entries =
174 Self::read_legacy(replica, id).map_err(read::Error::LegacyRead)?;
175
176 if version_entries.is_empty() {
177 return Err(read::Error::EmptyIdentity)?;
178 }
179 let last_version_clocks = mem::take(&mut version_entries.last_mut().expect("Exists").times);
182
183 let operations: Operations<Identity> = {
184 let mut ops: Vec<Operation<Identity>> = vec![];
185
186 let mut first_version = version_entries.remove(0);
187 ops.push(
188 Operation::<Identity>::from_value(
189 json_typed! { owned,
190 {
191 "type": u64::from(IdentityOperationType::Create),
192 "timestamp": first_version.unix_time,
193 "nonce": Into::<String>::into(first_version.nonce),
194 "name": first_version.name.unwrap_or("<Unnamed identity>".to_owned()),
195 }
196 },
197 IdentityStub { id },
198 )
199 .expect("The json is hard-coded"),
200 );
201
202 first_version.name = None;
204
205 for version in iter::once(first_version).chain(version_entries.into_iter()) {
206 if let Some(name) = version.name {
207 ops.push(
208 Operation::<Identity>::from_value(
209 json_typed! {owned,
210 {
211 "type": u64::from(IdentityOperationType::SetName),
212 "timestamp": version.unix_time,
213 "nonce": Into::<String>::into(version.nonce),
214 "name": name,
215 }
216 },
217 IdentityStub { id },
218 )
219 .expect("The json is hard-coded"),
220 );
221 }
222 if let Some(email) = version.email {
223 ops.push(
224 Operation::<Identity>::from_value(
225 json_typed! {owned,
226 {
227 "type": u64::from(IdentityOperationType::SetEmail),
228 "timestamp": version.unix_time,
229 "nonce": Into::<String>::into(version.nonce),
230 "email": email,
231 }
232 },
233 IdentityStub { id },
234 )
235 .expect("The json is hard-coded"),
236 );
237 }
238 if let Some(login_name) = version.login {
239 ops.push(
240 Operation::<Identity>::from_value(
241 json_typed! {owned,
242 {
243 "type": u64::from(IdentityOperationType::SetLoginName),
244 "timestamp": version.unix_time,
245 "nonce": Into::<String>::into(version.nonce),
246 "login_name": login_name,
247 }
248 },
249 IdentityStub { id },
250 )
251 .expect("The json is hard-coded"),
252 );
253 }
254 if let Some(avatar_url) = version.avatar_url {
255 ops.push(
256 Operation::<Identity>::from_value(
257 json_typed! {owned,
258 {
259 "type": u64::from(IdentityOperationType::SetAvatarUrl),
260 "timestamp": version.unix_time,
261 "nonce": Into::<String>::into(version.nonce),
262 "url": avatar_url.to_string(),
263 }
264 },
265 IdentityStub { id },
266 )
267 .expect("The json is hard-coded"),
268 );
269 }
270 if let Some(metadata) = version.metadata {
271 let mut newer_metadata = owned::Object::new();
272 unsafe {
273 for (key, value) in metadata {
274 newer_metadata.insert_nocheck(key, value.into());
277 }
278 }
279
280 ops.push(
281 Operation::<Identity>::from_value(
282 json_typed! {owned,
283 {
284 "type": u64::from(IdentityOperationType::SetMetadata),
285 "timestamp": version.unix_time,
286 "nonce": Into::<String>::into(version.nonce),
287 "metadata": owned::Value::Object(Box::new(newer_metadata))
288
289 }
290 },
291 IdentityStub { id },
292 )
293 .expect("The json is hard-coded"),
294 );
295 }
296 }
297 Operations::<Identity>::from_operations(ops)?
298 };
299
300 let (create_time, edit_time) = {
301 let create = last_version_clocks
309 .iter()
310 .find_map(|(key, value)| {
311 if key == "bugs-create" {
312 Some(value)
313 } else {
314 None
315 }
316 })
317 .unwrap_or(&1);
318 let edit = last_version_clocks
319 .iter()
320 .find_map(|(key, value)| {
321 if key == "bugs-edit" {
322 Some(value)
323 } else {
324 None
325 }
326 })
327 .unwrap_or(&1);
328
329 (lamport::Time::from(*create), lamport::Time::from(*edit))
330 };
331
332 let me = Self {
335 id,
336 operations,
337 create_time,
338 edit_time,
339 current_head: last_id,
340 };
341
342 impl_cache! {@populate replica.db(), key.as_slice(), &me}
343
344 Ok(me)
345 }
346}