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}