/ src / api / json / diff.rs
diff.rs
  1  use radicle_surf::{self as surf};
  2  use serde_json::{json, Value};
  3  
  4  use radicle::cob;
  5  
  6  pub(crate) struct Diff<'a>(&'a surf::diff::Diff);
  7  
  8  impl<'a> Diff<'a> {
  9      pub fn new(diff: &'a surf::diff::Diff) -> Self {
 10          Self(diff)
 11      }
 12  
 13      pub fn as_json(&self) -> Value {
 14          let s = self.0.stats();
 15          json!({
 16              "files": self.0.files().map(|f| {
 17                  match f {
 18                      surf::diff::FileDiff::Added(added) => json!({
 19                          "status": "added",
 20                          "path": added.path,
 21                          "diff": DiffContent::new(&added.diff).as_json(),
 22                          "new": DiffFile::new(&added.new).as_json(),
 23                      }),
 24                      surf::diff::FileDiff::Deleted(deleted) => json!({
 25                          "status": "deleted",
 26                          "path": deleted.path,
 27                          "diff": DiffContent::new(&deleted.diff).as_json(),
 28                          "old": DiffFile::new(&deleted.old).as_json(),
 29                      }),
 30                      surf::diff::FileDiff::Modified(modified) => json!({
 31                          "status": "modified",
 32                          "path": modified.path,
 33                          "diff": DiffContent::new(&modified.diff).as_json(),
 34                          "old": DiffFile::new(&modified.old).as_json(),
 35                          "new": DiffFile::new(&modified.new).as_json(),
 36                      }),
 37                      surf::diff::FileDiff::Moved(moved) => {
 38                          if moved.old == moved.new {
 39                              json!({
 40                                  "status": "moved",
 41                                  "oldPath": moved.old_path,
 42                                  "newPath": moved.new_path,
 43                                  "current": DiffFile::new(&moved.new).as_json(),
 44                              })
 45                          } else {
 46                              json!({
 47                                  "status": "moved",
 48                                  "oldPath": moved.old_path,
 49                                  "newPath": moved.new_path,
 50                                  "old": DiffFile::new(&moved.old).as_json(),
 51                                  "new": DiffFile::new(&moved.new).as_json(),
 52                                  "diff": DiffContent::new(&moved.diff).as_json()
 53                              })
 54                          }
 55                      },
 56                      surf::diff::FileDiff::Copied(copied) => {
 57                          if copied.old == copied.new {
 58                              json!({
 59                                  "status": "copied",
 60                                  "oldPath": copied.old_path,
 61                                  "newPath": copied.new_path,
 62                                  "current": DiffFile::new(&copied.new).as_json()
 63                              })
 64                          } else {
 65                              json!({
 66                                  "status": "copied",
 67                                  "oldPath": copied.old_path,
 68                                  "newPath": copied.new_path,
 69                                  "old": DiffFile::new(&copied.old).as_json(),
 70                                  "new": DiffFile::new(&copied.new).as_json(),
 71                                  "diff": DiffContent::new(&copied.diff).as_json()
 72                              })
 73                          }
 74                      },
 75                  }
 76              }).collect::<Vec<_>>(),
 77              "stats": json!({
 78                   "filesChanged": s.files_changed,
 79                   "insertions": s.insertions,
 80                   "deletions": s.deletions,
 81              }),
 82          })
 83      }
 84  }
 85  
 86  pub(crate) struct CodeLocation<'a>(&'a cob::CodeLocation);
 87  
 88  impl<'a> CodeLocation<'a> {
 89      pub fn new(location: &'a cob::CodeLocation) -> Self {
 90          Self(location)
 91      }
 92  
 93      pub fn as_json(&self) -> Value {
 94          match (&self.0.old, &self.0.new) {
 95              (Some(old), Some(new)) => json!({
 96                  "commit": self.0.commit,
 97                  "path": self.0.path,
 98                  "old": code_range(old),
 99                  "new": code_range(new)
100              }),
101              (None, Some(new)) => json!({
102                  "commit": self.0.commit,
103                  "path": self.0.path,
104                  "new": code_range(new)
105              }),
106              (Some(old), None) => json!({
107                  "commit": self.0.commit,
108                  "path": self.0.path,
109                  "old": code_range(old)
110              }),
111              (None, None) => json!({
112                  "commit": self.0.commit,
113                  "path": self.0.path
114              }),
115          }
116      }
117  }
118  
119  fn code_range(range: &cob::CodeRange) -> Value {
120      match range {
121          cob::CodeRange::Lines { range } => json!({
122              "type": "lines",
123              "range": range
124          }),
125          cob::CodeRange::Chars { line, range } => {
126              json!({ "type": "chars", "line": line, "range": range })
127          }
128      }
129  }
130  
131  pub(crate) struct DiffContent<'a>(&'a surf::diff::DiffContent);
132  
133  impl<'a> DiffContent<'a> {
134      pub fn new(value: &'a surf::diff::DiffContent) -> Self {
135          Self(value)
136      }
137  
138      pub fn as_json(&self) -> Value {
139          match self.0 {
140              surf::diff::DiffContent::Binary => json!({ "type": "binary" }),
141              surf::diff::DiffContent::Empty => json!({ "type": "empty" }),
142              surf::diff::DiffContent::Plain { hunks, stats, eof } => {
143                  let hunks = hunks
144                      .iter()
145                      .map(|h| Hunk::new(h).as_json())
146                      .collect::<Vec<_>>();
147  
148                  json!({
149                      "type": "plain",
150                      "hunks": hunks,
151                      "stats": json!({
152                          "additions": stats.additions,
153                          "deletions": stats.deletions
154                      }),
155                      "eof": match eof {
156                          surf::diff::EofNewLine::OldMissing => "oldMissing",
157                          surf::diff::EofNewLine::NewMissing => "newMissing",
158                          surf::diff::EofNewLine::BothMissing => "bothMissing",
159                          surf::diff::EofNewLine::NoneMissing => "noneMissing",
160                      }
161                  })
162              }
163          }
164      }
165  }
166  
167  pub(crate) struct DiffFile<'a>(&'a surf::diff::DiffFile);
168  
169  impl<'a> DiffFile<'a> {
170      pub fn new(value: &'a surf::diff::DiffFile) -> Self {
171          Self(value)
172      }
173  
174      pub fn as_json(&self) -> Value {
175          json!({ "oid": self.0.oid, "mode": match self.0.mode {
176              surf::diff::FileMode::Blob => "blob",
177              surf::diff::FileMode::BlobExecutable => "blobExecutable",
178              surf::diff::FileMode::Tree => "tree",
179              surf::diff::FileMode::Link => "link",
180              surf::diff::FileMode::Commit => "commit",
181          } })
182      }
183  }
184  
185  pub(crate) struct Modification<'a>(&'a surf::diff::Modification);
186  
187  impl<'a> Modification<'a> {
188      pub fn new(value: &'a surf::diff::Modification) -> Self {
189          Self(value)
190      }
191  
192      pub fn as_json(&self) -> Value {
193          match self.0 {
194              surf::diff::Modification::Addition(addition) => {
195                  json!({
196                      "type": "addition",
197                      "line": addition.line.clone().from_utf8().ok(),
198                      "lineNo": addition.line_no
199                  })
200              }
201              surf::diff::Modification::Deletion(deletion) => {
202                  json!({
203                      "type": "deletion",
204                      "line": deletion.line.clone().from_utf8().ok(),
205                      "lineNo": deletion.line_no
206                  })
207              }
208              surf::diff::Modification::Context {
209                  line,
210                  line_no_old,
211                  line_no_new,
212              } => {
213                  json!({
214                      "type": "context",
215                      "line": line.clone().from_utf8().ok(),
216                      "lineNoOld": line_no_old,
217                      "lineNoNew": line_no_new
218                  })
219              }
220          }
221      }
222  }
223  
224  pub(crate) struct Hunk<'a>(&'a surf::diff::Hunk<surf::diff::Modification>);
225  
226  impl<'a> Hunk<'a> {
227      pub fn new(value: &'a surf::diff::Hunk<surf::diff::Modification>) -> Self {
228          Self(value)
229      }
230  
231      pub fn as_json(&self) -> Value {
232          let lines = self
233              .0
234              .lines
235              .iter()
236              .map(|line| Modification::new(line).as_json())
237              .collect::<Vec<_>>();
238  
239          json!({
240              "header": self.0.header,
241              "lines": lines,
242              "old": self.0.old,
243              "new": self.0.new,
244          })
245      }
246  }