use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
JsonSchema,
arbitrary::Arbitrary,
)]
#[serde(tag = "type", rename_all = "snake_case")]
#[schemars(rename = "laboratories.filetree.FileTreeNode")]
pub enum FileTreeNode {
#[schemars(title = "File")]
File {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
#[arbitrary(with = crate::arbitrary_util::arbitrary_option_u64)]
size: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
#[arbitrary(with = crate::arbitrary_util::arbitrary_option_i64)]
created_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
#[arbitrary(with = crate::arbitrary_util::arbitrary_option_i64)]
modified_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
created_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
modified_by: Option<String>,
},
#[schemars(title = "Directory")]
Directory {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
#[arbitrary(with = crate::arbitrary_util::arbitrary_option_i64)]
created_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
#[arbitrary(with = crate::arbitrary_util::arbitrary_option_i64)]
modified_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
created_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
modified_by: Option<String>,
children: Vec<FileTreeNode>,
},
#[schemars(title = "Symlink")]
Symlink {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
target: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
#[arbitrary(with = crate::arbitrary_util::arbitrary_option_i64)]
created_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
#[arbitrary(with = crate::arbitrary_util::arbitrary_option_i64)]
modified_at: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
created_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
modified_by: Option<String>,
},
}
impl FileTreeNode {
pub fn name(&self) -> &str {
match self {
FileTreeNode::File { name, .. }
| FileTreeNode::Directory { name, .. }
| FileTreeNode::Symlink { name, .. } => name,
}
}
pub fn children_mut(&mut self) -> Option<&mut Vec<FileTreeNode>> {
match self {
FileTreeNode::Directory { children, .. } => Some(children),
_ => None,
}
}
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
JsonSchema,
arbitrary::Arbitrary,
)]
#[serde(tag = "type", rename_all = "snake_case")]
#[schemars(rename = "laboratories.filetree.FileTreeEvent")]
pub enum FileTreeEvent {
#[schemars(title = "Snapshot")]
Snapshot {
children: Vec<FileTreeNode>,
},
#[schemars(title = "Upserted")]
Upserted {
path: Vec<String>,
node: FileTreeNode,
},
#[schemars(title = "Removed")]
Removed {
path: Vec<String>,
},
}
impl FileTreeEvent {
pub fn apply(self, root: &mut Vec<FileTreeNode>) {
match self {
FileTreeEvent::Snapshot { children } => {
*root = children;
}
FileTreeEvent::Upserted { path, node } => {
let Some((leaf, parents)) = path.split_last() else {
return;
};
let siblings = descend_mut(root, parents);
match siblings.iter().position(|c| c.name() == leaf) {
Some(i) => siblings[i] = node,
None => siblings.push(node),
}
}
FileTreeEvent::Removed { path } => {
let Some((leaf, parents)) = path.split_last() else {
return;
};
let siblings = descend_mut(root, parents);
siblings.retain(|c| c.name() != leaf);
}
}
}
}
fn descend_mut<'a>(
mut children: &'a mut Vec<FileTreeNode>,
comps: &[String],
) -> &'a mut Vec<FileTreeNode> {
for comp in comps {
let idx = match children.iter().position(|c| c.name() == comp) {
Some(i) => i,
None => {
children.push(FileTreeNode::Directory {
name: comp.clone(),
created_at: None,
modified_at: None,
created_by: None,
modified_by: None,
children: Vec::new(),
});
children.len() - 1
}
};
if children[idx].children_mut().is_none() {
children[idx] = FileTreeNode::Directory {
name: comp.clone(),
created_at: None,
modified_at: None,
created_by: None,
modified_by: None,
children: Vec::new(),
};
}
children = children[idx].children_mut().expect("just ensured directory");
}
children
}