Skip to main content

katana_markdown_engine/metadata/
resolver.rs

1use super::types::{
2    ConflictedTarget, MetadataDocument, MetadataEntry, MetadataReconcileRequest,
3    MetadataReconcileResult, TargetResolution, TargetResolutionKind, UnresolvedTarget,
4};
5use crate::{KmeDocument, KmeNode, TextFingerprint};
6
7pub struct MetadataResolver;
8
9impl MetadataResolver {
10    pub fn new() -> Self {
11        Self
12    }
13
14    pub fn reconcile(
15        &self,
16        old_document: &KmeDocument,
17        new_document: &KmeDocument,
18        metadata: &MetadataDocument,
19    ) -> Vec<TargetResolution> {
20        metadata
21            .entries
22            .iter()
23            .map(|entry| self.resolve_entry(old_document, new_document, entry))
24            .collect()
25    }
26
27    pub fn reconcile_request(&self, request: MetadataReconcileRequest) -> MetadataReconcileResult {
28        let resolutions = self.reconcile(
29            &request.old_document,
30            &request.new_document,
31            &request.metadata,
32        );
33        MetadataReconcileResult {
34            metadata: request.metadata,
35            resolutions,
36        }
37    }
38
39    fn resolve_entry(
40        &self,
41        old_document: &KmeDocument,
42        new_document: &KmeDocument,
43        entry: &MetadataEntry,
44    ) -> TargetResolution {
45        if let Some(node) = new_document.node_by_id(&entry.target.node_id) {
46            return Self::resolved(entry, node);
47        }
48        let candidates = self.find_by_fingerprint(new_document, &entry.target.text_fingerprint);
49        match candidates.as_slice() {
50            [node] => return Self::moved(entry, node),
51            [_, _, ..] => return Self::conflict(entry, &candidates),
52            [] => {}
53        }
54        Self::unresolved(old_document, entry)
55    }
56
57    fn find_by_fingerprint<'a>(
58        &self,
59        document: &'a KmeDocument,
60        fingerprint: &TextFingerprint,
61    ) -> Vec<&'a KmeNode> {
62        document
63            .nodes
64            .iter()
65            .filter(|node| &node.source.raw.fingerprint() == fingerprint)
66            .collect()
67    }
68
69    fn resolved(entry: &MetadataEntry, node: &KmeNode) -> TargetResolution {
70        TargetResolution {
71            key: entry.key.clone(),
72            kind: TargetResolutionKind::Resolved {
73                node_id: node.id.clone(),
74            },
75        }
76    }
77
78    fn moved(entry: &MetadataEntry, node: &KmeNode) -> TargetResolution {
79        TargetResolution {
80            key: entry.key.clone(),
81            kind: TargetResolutionKind::Moved {
82                previous_node_id: entry.target.node_id.clone(),
83                node_id: node.id.clone(),
84            },
85        }
86    }
87
88    fn conflict(entry: &MetadataEntry, nodes: &[&KmeNode]) -> TargetResolution {
89        TargetResolution {
90            key: entry.key.clone(),
91            kind: TargetResolutionKind::Conflict(ConflictedTarget {
92                previous_node_id: entry.target.node_id.clone(),
93                candidate_node_ids: nodes.iter().map(|node| node.id.clone()).collect(),
94                reason: "multiple fingerprint matches".to_string(),
95            }),
96        }
97    }
98
99    fn unresolved(old_document: &KmeDocument, entry: &MetadataEntry) -> TargetResolution {
100        let old_exists = old_document.node_by_id(&entry.target.node_id).is_some();
101        let reason = if old_exists {
102            "target disappeared"
103        } else {
104            "target unknown"
105        };
106        TargetResolution {
107            key: entry.key.clone(),
108            kind: TargetResolutionKind::Unresolved(UnresolvedTarget {
109                node_id: entry.target.node_id.clone(),
110                reason: reason.to_string(),
111            }),
112        }
113    }
114}
115
116impl Default for MetadataResolver {
117    fn default() -> Self {
118        Self::new()
119    }
120}