use mf_model::{
mark::Mark, node::Node, node_definition::NodeTree, node_pool::NodePool,
types::NodeId,
};
use serde::Serialize;
use mf_model::rpds::{HashTrieMapSync, VectorSync};
#[derive(Debug, Clone, Serialize)]
pub struct IndexDoc {
pub node_id: String,
pub node_type: String,
pub parent_id: Option<String>,
pub marks: Vec<String>,
pub marks_json: String,
pub attrs_flat: Vec<(String, String)>,
pub attrs_json: String,
pub text: Option<String>,
pub path: Vec<String>,
pub order_i64: Option<i64>,
pub created_at_i64: Option<i64>,
pub updated_at_i64: Option<i64>,
}
impl IndexDoc {
pub fn to_node(&self) -> anyhow::Result<Node> {
let marks: VectorSync<Mark> =
serde_json::from_str(&self.marks_json).unwrap_or_default();
let attrs_map: HashTrieMapSync<String, serde_json::Value> =
serde_json::from_str(&self.attrs_json).unwrap_or_default();
let node = Node {
id: self.node_id.as_str().into(),
r#type: self.node_type.clone(),
attrs: mf_model::attrs::Attrs::from(attrs_map),
content: VectorSync::new_sync(), marks: marks.into(),
};
Ok(node)
}
pub fn from_node(
pool: &NodePool,
node: &Node,
) -> Self {
let parent_id = pool.parent_id(&node.id).cloned();
let marks: Vec<String> =
node.marks.iter().map(|m: &Mark| m.r#type.clone()).collect();
let marks_json = serde_json::to_string(&node.marks)
.unwrap_or_else(|_| "[]".to_string());
let mut attrs_flat = Vec::with_capacity(node.attrs.attrs.keys().len());
for (k, v) in node.attrs.attrs.iter() {
attrs_flat.push((k.clone(), flatten_value(v)));
}
let attrs_json = serde_json::to_string(&node.attrs.attrs)
.unwrap_or_else(|_| "{}".to_string());
let path: Vec<String> = pool
.get_node_path(&node.id)
.into_iter()
.map(|id| id.to_string())
.collect();
let text = extract_text(node);
let order_i64 = extract_i64(node, "order");
let created_at_i64 = extract_i64(node, "created_at");
let updated_at_i64 = extract_i64(node, "updated_at");
IndexDoc {
node_id: node.id.to_string(),
node_type: node.r#type.clone(),
parent_id: parent_id.map(|id| id.to_string()),
marks,
marks_json,
attrs_flat,
attrs_json,
text,
path,
order_i64,
created_at_i64,
updated_at_i64,
}
}
}
fn flatten_value(v: &serde_json::Value) -> String {
match v {
serde_json::Value::Null => "null".to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::String(s) => s.clone(),
_ => serde_json::to_string(v).unwrap_or_default(),
}
}
fn extract_text(node: &Node) -> Option<String> {
for key in ["text", "title", "content"] {
if let Some(serde_json::Value::String(s)) = node.attrs.get(key) {
if !s.is_empty() {
return Some(s.clone());
}
}
}
None
}
fn extract_i64(
node: &Node,
key: &str,
) -> Option<i64> {
node.attrs.get(key).and_then(|v| match v {
serde_json::Value::Number(n) => n
.as_i64()
.or_else(|| n.as_u64().and_then(|u| i64::try_from(u).ok())),
serde_json::Value::String(s) => s.parse::<i64>().ok(),
_ => None,
})
}
pub fn collect_node_ids_from_enum(node_enum: &NodeTree) -> Vec<NodeId> {
let mut ids: Vec<NodeId> = vec![node_enum.0.id.clone()];
for child in &node_enum.1 {
ids.extend(collect_node_ids_from_enum(child));
}
ids
}