use std::str::FromStr;
use simd_json::{
base::ValueTryAsMutObject,
derived::{ValueTryAsScalar, ValueTryIntoObject, ValueTryIntoString},
owned,
};
use url::Url;
use super::Identity;
use crate::replica::{
Replica,
entity::{EntityRead, id::entity_id::EntityId, nonce::Nonce},
};
#[derive(Debug)]
pub struct VersionEntry {
pub version: u64,
pub times: Vec<(String, u64)>,
pub unix_time: u64,
pub metadata: Option<Vec<(String, String)>>,
pub name: Option<String>,
pub email: Option<String>,
pub login: Option<String>,
pub avatar_url: Option<Url>,
pub(super) nonce: Nonce,
}
impl Identity {
#[allow(clippy::missing_panics_doc)]
#[allow(clippy::too_many_lines)]
pub fn read_legacy(
replica: &Replica,
id: EntityId<Self>,
) -> Result<Vec<VersionEntry>, read::Error> {
const VERSION_ENTRY_NAME: &str = "version";
let root_id = Identity::last_git_commit(replica, id)?;
let bfs_order = Identity::breadth_first_search(replica.repo(), root_id)?;
if bfs_order.is_empty() {
return Err(read::Error::Empty);
}
let mut version_entries = vec![];
let mut is_first_commit = true;
for commit in bfs_order.into_iter().rev() {
if !is_first_commit && commit.parent_ids().count() == 0 {
return Err(read::Error::MultipleLeafs);
}
{
let tree = commit.tree()?;
for entry in tree.iter() {
let entry = entry.map_err(|err| {
#[allow(clippy::unit_cmp)]
{
assert_eq!(err.inner, ());
}
read::Error::FailedTreeEntryDecode()
})?;
let mut object = if entry.filename() == VERSION_ENTRY_NAME {
entry.object().map_err(|err| read::Error::MissingObject {
id: id.as_id(),
error: err,
})?
} else {
return Err(read::Error::UnknownEntityName(entry.filename().to_owned()));
};
let entry: VersionEntry = {
use crate::replica::entity::operation::operation_data::get;
let mut value =
simd_json::to_owned_value(&mut object.data).map_err(|err| {
read::Error::InvalidJsonVersionEntry {
got: String::from_utf8_lossy(&(object.data.clone()))
.to_string(),
error: err,
}
})?;
let object = value.try_as_object_mut()?;
VersionEntry {
version: get! {object, "version", try_as_u64, read::Error},
times: get! {
@map[preserve-order] object,
"times", try_as_u64, read::Error},
unix_time: get! {object, "unix_time", try_as_u64, read::Error},
metadata: get! {@option[next] object, "metadata", |some: owned::Value| {
let object = some.try_into_object()?;
Ok::<_, read::Error>(
Some(get! {@mk_map object, try_into_string, read::Error}))
}, read::Error},
name: get! {@option object, "name", try_into_string, read::Error},
email: get! {@option object, "email", try_into_string, read::Error},
login: get! {@option object, "login", try_into_string, read::Error},
avatar_url:
get! {@option object, "avatar_url", try_into_string, read::Error}
.map(|str| Url::from_str(&str))
.transpose()?,
nonce: Nonce::try_from(
get! {object, "nonce", try_into_string, read::Error},
)?,
}
};
if entry.version != 2 {
return Err(read::Error::VersionMismatch {
got: entry.version,
expected: 2,
});
}
version_entries.push(entry);
}
}
is_first_commit = false;
}
Ok(version_entries)
}
}
#[allow(missing_docs)]
pub mod read {
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
ReferenceResolve(#[from] crate::replica::entity::find::Error),
#[error(transparent)]
BreadthFirstSearch(#[from] crate::replica::entity::bfs::Error),
#[error("This identity has no recorded legacy versions.")]
Empty,
#[error("Multiple leafs in the identity version DAG")]
MultipleLeafs,
#[error(
"Expected to find an tree with the legacy identity commit, but found none. Error: {0}"
)]
MissingTree(#[from] gix::object::commit::Error),
#[error("Failed to decode an entry from the legacy identity tree to access.")]
FailedTreeEntryDecode(),
#[error("Found unknown entity {0}, while decoding legacy identity tree.")]
UnknownEntityName(gix::bstr::BString),
#[error(
"Failed to find the object blob for the legacy identity version tree entry of {id}, \
because: {error}"
)]
MissingObject {
id: crate::replica::entity::id::Id,
error: gix::objs::find::existing::Error,
},
#[error("Failed to parse the legacy idenity version entry json ({got}), because: {error}")]
InvalidJsonVersionEntry {
got: String,
error: simd_json::Error,
},
#[error("Failed to get the expected json field: {field}")]
MissingJsonField { field: &'static str },
#[error("Failed to parse the field '{field}' as correct type: {err}")]
WrongJsonType {
err: simd_json::TryTypeError,
field: &'static str,
},
#[error("Expected a json object but got something else: {0}")]
ExpectedJsonObject(#[from] simd_json::TryTypeError),
#[error("Failed to decode the base64 nonce: {0}")]
NonceParse(#[from] base64::DecodeSliceError),
#[error("Failed to parse the avatar url: {0}")]
UrlParse(#[from] url::ParseError),
#[error(
"The legacy identity entry json's version did not match. Got {got}, but expected \
{expected}"
)]
VersionMismatch { got: u64, expected: i32 },
#[error(
"The sequnce of operation composing this legacy identity ({id}), is invalid: {error}"
)]
InvalidOperationSequence {
id: crate::replica::entity::id::Id,
error: crate::replica::entity::operation::operations::create::Error<
crate::entities::identity::Identity,
>,
},
}
}