Skip to main content

atelier_sdk_diff/
model.rs

1use crate::package::PackageId;
2
3/// The rung a delta is carried at: the format-independent fidelity ladder.
4///
5/// The floor is [`Fidelity::Binary`] (changed-or-not on opaque bytes); the
6/// ladder raises a delta to [`Fidelity::Text`] (a line diff over projected
7/// text) and, once a package ships a differ, [`Fidelity::Rich`] (deltas in
8/// the format's own terms).
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Fidelity {
11    /// The floor: changed-or-not over opaque bytes. Every document diffs
12    /// at least here.
13    Binary,
14    /// A line diff over projected or plain text.
15    Text,
16    /// A delta in the format's own terms, produced by a package differ.
17    Rich,
18}
19
20/// What kind of change one [`Delta`] records.
21///
22/// `Moved` is part of the model but is never produced at the binary rung:
23/// rename detection belongs to the engine, not to a content-id comparison.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum DeltaKind {
26    /// The address exists only on the after side.
27    Added,
28    /// The address exists only on the before side.
29    Removed,
30    /// The address exists on both sides with different content.
31    Changed,
32    /// The content moved to a new address.
33    Moved,
34}
35
36/// Where a delta lands, in the format's own terms.
37///
38/// At the binary rung this is a workspace-relative path. Richer addresses —
39/// a cell, a clause, a paragraph — arrive with format packages later.
40#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
41pub struct Address(pub String);
42
43impl Address {
44    /// An address from a workspace-relative path or format-native locator.
45    pub fn new(path: impl Into<String>) -> Self {
46        Self(path.into())
47    }
48
49    /// The address as its underlying string.
50    #[must_use]
51    pub fn as_str(&self) -> &str {
52        &self.0
53    }
54}
55
56/// What happened to one line at the text rung.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum LineKind {
59    /// The line exists only on the after side.
60    Added,
61    /// The line exists only on the before side.
62    Removed,
63    /// The synthetic marker after a changed line with no trailing newline
64    /// — its own kind, so document content that happens to contain the
65    /// marker text can never be mistaken for it.
66    NoNewline,
67}
68
69/// One line of a text-rung comparison: what happened and the line's content.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct Line {
72    /// What happened to the line.
73    pub kind: LineKind,
74    /// The line's content, without its trailing newline.
75    pub text: String,
76}
77
78/// One addressed difference inside a [`Diff`], carried at its own rung.
79///
80/// `before` and `after` hold content ids, not content: the identity of the
81/// bytes on each side, absent when the side does not exist. `lines` carries
82/// the text-rung comparison and is empty at the binary rung. `package`
83/// names the format package whose projection or differ produced the delta —
84/// outputs carry the package version (ADR-0003) — and is `None` at rungs no
85/// package produced: the binary floor and plain-text raises. `summary` is a
86/// rich delta's difference in the format's own terms — the affected text
87/// and the property change — and `None` below the rich rung.
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct Delta {
90    /// Where the delta lands, in the format's own terms.
91    pub address: Address,
92    /// What kind of change the delta records.
93    pub kind: DeltaKind,
94    /// The rung the delta is carried at.
95    pub fidelity: Fidelity,
96    /// The content id of the before side; `None` when the side does not exist.
97    pub before: Option<String>,
98    /// The content id of the after side; `None` when the side does not exist.
99    pub after: Option<String>,
100    /// The text-rung line comparison; empty at the binary rung.
101    pub lines: Vec<Line>,
102    /// The package whose projection or differ produced the delta; `None`
103    /// at rungs no package produced.
104    pub package: Option<PackageId>,
105    /// A rich delta's difference in the format's own terms; `None` below
106    /// the rich rung.
107    pub summary: Option<String>,
108}
109
110impl Delta {
111    /// This delta raised to the text rung, carrying its line comparison and
112    /// the package that projected the compared text (`None` for plain text).
113    #[must_use]
114    pub fn at_text_rung(self, lines: Vec<Line>, package: Option<PackageId>) -> Self {
115        Self {
116            fidelity: Fidelity::Text,
117            lines,
118            package,
119            ..self
120        }
121    }
122
123    /// A rich-rung delta a format package produced: addressed in the
124    /// format's own terms, its difference described by `summary`.
125    #[must_use]
126    pub fn rich(address: Address, summary: impl Into<String>, package: PackageId) -> Self {
127        Self {
128            address,
129            kind: DeltaKind::Changed,
130            fidelity: Fidelity::Rich,
131            before: None,
132            after: None,
133            lines: Vec::new(),
134            package: Some(package),
135            summary: Some(summary.into()),
136        }
137    }
138}
139
140/// The differences between two versions: addressed deltas, each carried at
141/// the highest fidelity the ladder could raise it to.
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct Diff {
144    /// The addressed deltas, one per changed address.
145    pub deltas: Vec<Delta>,
146}