pub mod errors;
pub mod id;
pub use errors::EntryError;
pub use id::ID;
use rand::Rng;
use serde::{Deserialize, Serialize};
use crate::{Result, auth::types::SigInfo, constants::ROOT};
pub type RawData = String;
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct TreeNode {
pub root: ID,
pub parents: Vec<ID>,
pub metadata: Option<RawData>,
}
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
struct SubTreeNode {
pub name: String,
pub parents: Vec<ID>,
pub data: RawData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Entry {
tree: TreeNode,
subtrees: Vec<SubTreeNode>,
pub sig: SigInfo,
}
impl Entry {
pub fn builder(root: impl Into<ID>) -> EntryBuilder {
EntryBuilder::new(root)
}
pub fn root_builder() -> EntryBuilder {
EntryBuilder::new_top_level()
}
pub fn id(&self) -> ID {
let json = serde_json::to_string(self).expect("Failed to serialize entry for hashing");
ID::from_bytes(json)
}
pub fn root(&self) -> ID {
self.tree.root.clone()
}
pub fn is_root(&self) -> bool {
self.subtrees.iter().any(|node| node.name == ROOT) && self.tree.parents.is_empty()
}
pub fn in_subtree(&self, subtree_name: impl AsRef<str>) -> bool {
self.subtrees
.iter()
.any(|node| node.name == subtree_name.as_ref())
}
pub fn in_tree(&self, tree_id: impl AsRef<str>) -> bool {
self.root() == tree_id.as_ref() || (self.id().as_str() == tree_id.as_ref())
}
pub fn subtrees(&self) -> Vec<String> {
self.subtrees
.iter()
.map(|subtree| subtree.name.clone())
.collect()
}
pub fn metadata(&self) -> Option<&RawData> {
self.tree.metadata.as_ref()
}
pub fn data(&self, subtree_name: impl AsRef<str>) -> Result<&RawData> {
self.subtrees
.iter()
.find(|node| node.name == subtree_name.as_ref())
.map(|node| &node.data)
.ok_or_else(|| {
crate::store::StoreError::KeyNotFound {
store: "entry".to_string(),
key: subtree_name.as_ref().to_string(),
}
.into()
})
}
pub fn parents(&self) -> Result<Vec<ID>> {
Ok(self.tree.parents.clone())
}
pub fn subtree_parents(&self, subtree_name: impl AsRef<str>) -> Result<Vec<ID>> {
self.subtrees
.iter()
.find(|node| node.name == subtree_name.as_ref())
.map(|node| node.parents.clone())
.ok_or_else(|| {
crate::store::StoreError::KeyNotFound {
store: "entry".to_string(),
key: subtree_name.as_ref().to_string(),
}
.into()
})
}
pub fn canonical_for_signing(&self) -> Self {
let mut canonical = self.clone();
canonical.sig.sig = None;
canonical
}
pub fn canonical_bytes(&self) -> crate::Result<Vec<u8>> {
let json = serde_json::to_string(self).map_err(crate::Error::Serialize)?;
Ok(json.into_bytes())
}
pub fn signing_bytes(&self) -> crate::Result<Vec<u8>> {
self.canonical_for_signing().canonical_bytes()
}
fn validate_id_format(id: &ID, context: &str) -> crate::Result<()> {
if let Err(id_err) = ID::parse(id.as_str()) {
let contextual_err = match &id_err {
crate::entry::id::IdError::InvalidFormat(_) => {
crate::entry::id::IdError::InvalidFormat(format!(
"Invalid ID format in {}: {}",
context,
id.as_str()
))
}
crate::entry::id::IdError::InvalidHex(_) => crate::entry::id::IdError::InvalidHex(
format!("Invalid hex characters in {} ID: {}", context, id.as_str()),
),
_ => id_err,
};
return Err(contextual_err.into());
}
Ok(())
}
pub fn validate(&self) -> crate::Result<()> {
use crate::constants::{ROOT, SETTINGS};
use crate::instance::errors::InstanceError;
let has_root_marker = self.subtrees.iter().any(|node| node.name == ROOT);
if has_root_marker && !self.tree.parents.is_empty() {
return Err(InstanceError::EntryValidationFailed {
reason: format!(
"Entry {} has _root marker but also has parents. Root entries cannot have parent relationships as they are the starting points of trees.",
self.id()
),
}.into());
}
let is_root_entry = has_root_marker && self.tree.parents.is_empty();
if !self.tree.root.is_empty() {
Self::validate_id_format(&self.tree.root, "tree root ID")?;
}
for subtree_node in &self.subtrees {
let subtree_name = &subtree_node.name;
let subtree_parents = &subtree_node.parents;
if subtree_name == ROOT {
continue;
}
if !is_root_entry && subtree_parents.is_empty() {
tracing::debug!(
entry_id = %self.id(),
subtree = subtree_name,
"Entry has empty subtree parents - will be validated in transaction layer"
);
}
if subtree_name == SETTINGS && !is_root_entry && subtree_parents.is_empty() {
tracing::debug!(
entry_id = %self.id(),
"Settings subtree has empty parents - will be validated in transaction layer"
);
}
for parent_id in subtree_parents {
if parent_id.is_empty() {
return Err(InstanceError::EntryValidationFailed {
reason: format!(
"Entry {} has subtree '{}' with empty parent ID. Parent IDs must be non-empty valid entry IDs.",
self.id(),
subtree_name
),
}.into());
}
Self::validate_id_format(
parent_id,
&format!("subtree '{subtree_name}' parent ID"),
)?;
}
}
if !is_root_entry {
let main_parents = self.tree.parents.clone();
if main_parents.is_empty() {
return Err(InstanceError::EntryValidationFailed {
reason: format!(
"Non-root entry {} has empty main tree parents. All non-root entries must have valid parent relationships in the main tree.",
self.id()
),
}.into());
}
for parent_id in &main_parents {
if parent_id.is_empty() {
return Err(InstanceError::EntryValidationFailed {
reason: format!(
"Entry {} has empty parent ID in main tree. Parent IDs must be non-empty valid entry IDs.",
self.id()
),
}.into());
}
Self::validate_id_format(parent_id, "main tree parent ID")?;
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct EntryBuilder {
tree: TreeNode,
subtrees: Vec<SubTreeNode>,
sig: SigInfo,
}
impl EntryBuilder {
pub fn new(root: impl Into<ID>) -> Self {
Self {
tree: TreeNode {
root: root.into(),
parents: Vec::new(),
metadata: None,
},
subtrees: Vec::new(),
sig: SigInfo::default(),
}
}
pub fn new_top_level() -> Self {
let mut builder = Self::new("");
builder.set_subtree_data_mut(ROOT, "");
let entropy: u64 = rand::thread_rng().r#gen();
let metadata_json = format!(r#"{{"entropy":{entropy}}}"#);
builder.set_metadata_mut(&metadata_json);
builder
}
pub fn set_sig(mut self, sig: SigInfo) -> Self {
self.sig = sig;
self
}
pub fn set_sig_mut(&mut self, sig: SigInfo) -> &mut Self {
self.sig = sig;
self
}
pub fn subtrees(&self) -> Vec<String> {
self.subtrees
.iter()
.map(|subtree| subtree.name.clone())
.collect()
}
pub fn data(&self, subtree_name: impl AsRef<str>) -> Result<&RawData> {
self.subtrees
.iter()
.find(|node| node.name == subtree_name.as_ref())
.map(|node| &node.data)
.ok_or_else(|| {
crate::store::StoreError::KeyNotFound {
store: "entry".to_string(),
key: subtree_name.as_ref().to_string(),
}
.into()
})
}
pub fn parents(&self) -> Result<Vec<ID>> {
Ok(self.tree.parents.clone())
}
pub fn subtree_parents(&self, subtree_name: impl AsRef<str>) -> Result<Vec<ID>> {
self.subtrees
.iter()
.find(|node| node.name == subtree_name.as_ref())
.map(|node| node.parents.clone())
.ok_or_else(|| {
crate::store::StoreError::KeyNotFound {
store: "entry".to_string(),
key: subtree_name.as_ref().to_string(),
}
.into()
})
}
fn sort_parents_list(parents: &mut [ID]) {
parents.sort();
}
fn sort_subtrees_list(&mut self) {
self.subtrees.sort_by(|a, b| a.name.cmp(&b.name));
}
pub fn set_subtree_data(mut self, name: impl Into<String>, data: impl Into<RawData>) -> Self {
let name = name.into();
if let Some(node) = self.subtrees.iter_mut().find(|node| node.name == name) {
node.data = data.into();
} else {
self.subtrees.push(SubTreeNode {
name,
data: data.into(),
parents: vec![],
});
}
self
}
pub fn set_subtree_data_mut(
&mut self,
name: impl Into<String>,
data: impl Into<RawData>,
) -> &mut Self {
let name = name.into();
if let Some(node) = self.subtrees.iter_mut().find(|node| node.name == name) {
node.data = data.into();
} else {
self.subtrees.push(SubTreeNode {
name,
data: data.into(),
parents: vec![],
});
}
self
}
pub fn remove_empty_subtrees(mut self) -> Self {
self.subtrees
.retain(|subtree| !subtree.data.is_empty() && subtree.data != "{}");
self
}
pub fn remove_empty_subtrees_mut(&mut self) -> &mut Self {
self.subtrees
.retain(|subtree| !subtree.data.is_empty() && subtree.data != "{}");
self
}
pub fn set_root(mut self, root: impl Into<String>) -> Self {
self.tree.root = root.into().into();
self
}
pub fn set_root_mut(&mut self, root: impl Into<String>) -> &mut Self {
self.tree.root = root.into().into();
self
}
pub fn set_parents(mut self, parents: Vec<ID>) -> Self {
self.tree.parents = parents;
self
}
pub fn set_parents_mut(&mut self, parents: Vec<ID>) -> &mut Self {
self.tree.parents = parents;
self
}
pub fn add_parent(mut self, parent_id: impl Into<String>) -> Self {
self.tree.parents.push(parent_id.into().into());
self
}
pub fn add_parent_mut(&mut self, parent_id: impl Into<String>) -> &mut Self {
self.tree.parents.push(parent_id.into().into());
self
}
pub fn get_parents(&self) -> Option<&Vec<ID>> {
if self.tree.parents.is_empty() {
None
} else {
Some(&self.tree.parents)
}
}
pub fn set_subtree_parents(
mut self,
subtree_name: impl Into<String>,
parents: Vec<ID>,
) -> Self {
let subtree_name = subtree_name.into();
if let Some(node) = self
.subtrees
.iter_mut()
.find(|node| node.name == subtree_name)
{
node.parents = parents;
} else {
self.subtrees.push(SubTreeNode {
name: subtree_name,
data: "{}".to_owned(), parents,
});
}
self
}
pub fn set_subtree_parents_mut(
&mut self,
subtree_name: impl Into<String>,
parents: Vec<ID>,
) -> &mut Self {
let subtree_name = subtree_name.into();
if let Some(node) = self
.subtrees
.iter_mut()
.find(|node| node.name == subtree_name)
{
node.parents = parents;
} else {
self.subtrees.push(SubTreeNode {
name: subtree_name,
data: "{}".to_owned(), parents,
});
}
self
}
pub fn add_subtree_parent(
mut self,
subtree_name: impl Into<String>,
parent_id: impl Into<String>,
) -> Self {
let subtree_name = subtree_name.into();
let parent_id = parent_id.into();
if let Some(node) = self
.subtrees
.iter_mut()
.find(|node| node.name == subtree_name)
{
node.parents.push(parent_id.into());
} else {
self.subtrees.push(SubTreeNode {
name: subtree_name,
data: "{}".to_owned(),
parents: vec![parent_id.into()],
});
}
self
}
pub fn add_subtree_parent_mut(
&mut self,
subtree_name: impl Into<String>,
parent_id: impl Into<String>,
) -> &mut Self {
let subtree_name = subtree_name.into();
let parent_id = parent_id.into();
if let Some(node) = self
.subtrees
.iter_mut()
.find(|node| node.name == subtree_name)
{
node.parents.push(parent_id.into());
} else {
self.subtrees.push(SubTreeNode {
name: subtree_name,
data: "{}".to_owned(),
parents: vec![parent_id.into()],
});
}
self
}
pub fn set_metadata(mut self, metadata: impl Into<String>) -> Self {
self.tree.metadata = Some(metadata.into());
self
}
pub fn set_metadata_mut(&mut self, metadata: impl Into<String>) -> &mut Self {
self.tree.metadata = Some(metadata.into());
self
}
pub fn metadata(&self) -> Option<&RawData> {
self.tree.metadata.as_ref()
}
pub fn build(mut self) -> crate::Result<Entry> {
Self::sort_parents_list(&mut self.tree.parents);
for subtree in &mut self.subtrees {
Self::sort_parents_list(&mut subtree.parents);
}
self.tree.parents.dedup();
for subtree in &mut self.subtrees {
subtree.parents.dedup();
}
self.sort_subtrees_list();
let entry = Entry {
tree: self.tree,
subtrees: self.subtrees,
sig: self.sig,
};
entry.validate()?;
Ok(entry)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_root_entry_without_parents_succeeds() {
let entry = Entry::root_builder()
.build()
.expect("Root entry should build successfully");
assert!(
entry.validate().is_ok(),
"Root entry should be valid without parents"
);
assert!(entry.is_root(), "Entry should be identified as root");
}
#[test]
fn test_validate_root_entry_with_parents_fails() {
let result = Entry::root_builder()
.add_parent(ID::from_bytes("some_parent"))
.build();
assert!(
result.is_err(),
"Root entry with parents should fail to build"
);
}
#[test]
fn test_validate_non_root_entry_without_parents_fails() {
let result = Entry::builder(ID::from_bytes("tree")).build();
assert!(
result.is_err(),
"Non-root entry without parents should fail to build"
);
let error_msg = format!("{:?}", result.unwrap_err());
assert!(
error_msg.contains("EntryValidationFailed"),
"Should be EntryValidationFailed error, got: {error_msg}"
);
assert!(
error_msg.contains("empty main tree parents"),
"Error should mention empty parent requirement, got: {error_msg}"
);
}
#[test]
fn test_validate_non_root_entry_with_parents_succeeds() {
let entry = Entry::builder(ID::from_bytes("tree"))
.add_parent(ID::from_bytes("parent"))
.build()
.expect("Entry with parent should build successfully");
assert!(
entry.validate().is_ok(),
"Non-root entry with parents should be valid"
);
assert!(!entry.is_root(), "Entry should not be identified as root");
}
#[test]
fn test_validate_empty_parent_id_fails() {
let result = Entry::builder(ID::from_bytes("tree"))
.add_parent("") .build();
assert!(
result.is_err(),
"Entry with empty parent ID should fail to build"
);
let error_msg = format!("{:?}", result.unwrap_err());
assert!(
error_msg.contains("EntryValidationFailed"),
"Should be EntryValidationFailed error, got: {error_msg}"
);
assert!(
error_msg.contains("empty parent ID"),
"Error should mention empty parent ID, got: {error_msg}"
);
}
#[test]
fn test_validate_subtree_with_empty_parent_id_fails() {
let result = Entry::root_builder()
.set_subtree_data("messages", "test_data")
.set_subtree_parents("messages", vec!["".into()]) .build();
assert!(
result.is_err(),
"Entry with empty subtree parent ID should fail to build"
);
let error_msg = format!("{:?}", result.unwrap_err());
assert!(
error_msg.contains("EntryValidationFailed"),
"Should be EntryValidationFailed error, got: {error_msg}"
);
assert!(
error_msg.contains("empty parent ID"),
"Error should mention empty parent ID, got: {error_msg}"
);
}
#[test]
fn test_validate_non_root_with_empty_subtree_parents_logs_but_passes() {
let entry = Entry::builder(ID::from_bytes("tree"))
.add_parent(ID::from_bytes("main_parent"))
.set_subtree_data("messages", "test_data")
.set_subtree_parents("messages", vec![]) .build()
.expect("Entry with main parent should build successfully");
assert!(
entry.validate().is_ok(),
"Non-root entry with empty subtree parents should pass entry-level validation"
);
assert!(!entry.is_root(), "Entry should not be identified as root");
assert!(
!entry.parents().unwrap().is_empty(),
"Should have main tree parents"
);
assert!(
entry.subtree_parents("messages").unwrap().is_empty(),
"Should have empty subtree parents"
);
}
#[test]
fn test_validate_root_entry_with_empty_subtree_parents_succeeds() {
let entry = Entry::root_builder()
.set_subtree_data("messages", "test_data")
.set_subtree_parents("messages", vec![]) .build()
.expect("Root entry should build successfully");
assert!(
entry.validate().is_ok(),
"Root entry with empty subtree parents should be valid"
);
assert!(entry.is_root(), "Entry should be identified as root");
}
#[test]
fn test_validate_settings_subtree_follows_standard_rules() {
let root_entry = Entry::root_builder()
.set_subtree_data("_settings", "auth_config")
.set_subtree_parents("_settings", vec![]) .build()
.expect("Root entry should build successfully");
assert!(
root_entry.validate().is_ok(),
"Root entry with empty settings subtree parents should be valid"
);
let non_root_entry = Entry::builder(ID::from_bytes("tree"))
.add_parent(ID::from_bytes("main_parent"))
.set_subtree_data("_settings", "auth_config")
.set_subtree_parents("_settings", vec![]) .build()
.expect("Entry with main parent should build successfully");
assert!(
non_root_entry.validate().is_ok(),
"Non-root entry with empty settings subtree parents should pass entry validation"
);
}
#[test]
fn test_validate_multiple_subtrees_with_mixed_parent_scenarios() {
let entry = Entry::builder(ID::from_bytes("tree"))
.add_parent(ID::from_bytes("main_parent"))
.set_subtree_data("messages", "msg_data")
.set_subtree_parents("messages", vec![ID::from_bytes("msg_parent")]) .set_subtree_data("users", "user_data")
.set_subtree_parents("users", vec![]) .build()
.expect("Entry with main parent should build successfully");
assert!(
entry.validate().is_ok(),
"Entry with mixed subtree parent scenarios should pass entry validation"
);
assert!(
!entry.parents().unwrap().is_empty(),
"Should have main tree parents"
);
assert!(
!entry.subtree_parents("messages").unwrap().is_empty(),
"Messages should have parents"
);
assert!(
entry.subtree_parents("users").unwrap().is_empty(),
"Users should have empty parents"
);
}
#[test]
fn test_validate_root_subtree_marker_skipped() {
let entry = Entry::root_builder()
.set_subtree_data("other_subtree", "data")
.build()
.expect("Root entry should build successfully");
assert!(
entry.validate().is_ok(),
"Entry with _root marker subtree should pass validation"
);
assert!(
entry.subtrees().contains(&"_root".to_string()),
"Root entry should contain _root marker"
);
}
}