/ src / api / json / cobs.rs
cobs.rs
  1  use std::collections::BTreeMap;
  2  
  3  use serde_json::{json, Value};
  4  
  5  use radicle::cob;
  6  use radicle::cob::{issue, patch};
  7  use radicle_experiment_cob as experiment;
  8  use radicle::identity;
  9  use radicle::node::AliasStore;
 10  use radicle::storage::{git, refs, RemoteRepository};
 11  
 12  use super::thread;
 13  use super::{edit, reactions, Author};
 14  
 15  pub(crate) struct Issue<'a>(&'a issue::Issue);
 16  
 17  impl<'a> Issue<'a> {
 18      pub fn new(issue: &'a issue::Issue) -> Self {
 19          Self(issue)
 20      }
 21  
 22      pub fn as_json(&self, id: issue::IssueId, aliases: &impl AliasStore) -> Value {
 23          json!({
 24              "id": id.to_string(),
 25              "author": Author::new(self.0.author().id()).as_json(aliases),
 26              "title": self.0.title(),
 27              "state": self.0.state(),
 28              "assignees": self.0.assignees().map(|assignee|
 29                  Author::new(assignee).as_json(aliases)
 30              ).collect::<Vec<_>>(),
 31              "discussion": self.0.comments().map(|(id, c)|
 32                  thread::Comment::Issue(c).as_json(id, aliases)
 33              ).collect::<Vec<_>>(),
 34              "labels": self.0.labels().collect::<Vec<_>>(),
 35          })
 36      }
 37  }
 38  
 39  pub(crate) struct Patch<'a>(&'a patch::Patch);
 40  
 41  impl<'a> Patch<'a> {
 42      pub fn new(patch: &'a patch::Patch) -> Self {
 43          Self(patch)
 44      }
 45  
 46      pub fn as_json(
 47          &self,
 48          id: patch::PatchId,
 49          repo: &git::Repository,
 50          aliases: &impl AliasStore,
 51      ) -> Value {
 52          json!({
 53              "id": id.to_string(),
 54              "author": Author::new(self.0.author().id()).as_json(aliases),
 55              "title": self.0.title(),
 56              "state": self.0.state(),
 57              "target": self.0.target(),
 58              "labels": self.0.labels().collect::<Vec<_>>(),
 59              "merges": self.0.merges().map(|(nid, m)| json!({
 60                  "author": Author::new(&identity::Did::from(nid)).as_json(aliases),
 61                  "commit": m.commit,
 62                  "timestamp": m.timestamp.as_secs(),
 63                  "revision": m.revision,
 64              })).collect::<Vec<_>>(),
 65              "assignees": self.0.assignees().map(|assignee|
 66                  Author::new(&assignee).as_json(aliases)
 67              ).collect::<Vec<_>>(),
 68              "revisions": self.0.revisions().map(|(id, rev)|
 69                  Revision::new(rev).as_json(id, repo, aliases)
 70              ).collect::<Vec<_>>(),
 71          })
 72      }
 73  }
 74  
 75  pub(crate) struct Revision<'a>(&'a patch::Revision);
 76  
 77  impl<'a> Revision<'a> {
 78      pub fn new(revision: &'a patch::Revision) -> Self {
 79          Self(revision)
 80      }
 81  
 82      pub fn as_json(
 83          &self,
 84          id: patch::RevisionId,
 85          repo: &git::Repository,
 86          aliases: &impl AliasStore,
 87      ) -> Value {
 88          let reactions = self
 89              .0
 90              .reactions()
 91              .iter()
 92              .flat_map(|(location, reaction)| {
 93                  reactions(
 94                      reaction.iter().fold(
 95                          BTreeMap::new(),
 96                          |mut acc: BTreeMap<&cob::Reaction, Vec<_>>, (author, emoji)| {
 97                              acc.entry(emoji).or_default().push(author);
 98                              acc
 99                          },
100                      ),
101                      location.as_ref(),
102                      aliases,
103                  )
104              })
105              .collect::<Vec<_>>();
106          let refs = get_refs(repo, self.0.author().id(), &self.0.head()).unwrap_or_default();
107  
108          json!({
109              "id": id,
110              "author": Author::new(self.0.author().id()).as_json(aliases),
111              "description": self.0.description(),
112              "edits": self.0.edits().map(|e| edit(e, aliases)).collect::<Vec<_>>(),
113              "reactions": reactions,
114              "base": self.0.base(),
115              "oid": self.0.head(),
116              "refs": refs,
117              "discussions": self.0.discussion().comments().map(|(id, c)|
118                  thread::Comment::Patch(c).as_json(id, aliases)
119              ).collect::<Vec<_>>(),
120              "timestamp": self.0.timestamp().as_secs(),
121              "reviews": self.0.reviews().map(|(_, r)|
122                  Review::new(r).as_json(aliases)
123              ).collect::<Vec<_>>(),
124          })
125      }
126  }
127  
128  pub(crate) struct Review<'a>(&'a patch::Review);
129  
130  impl<'a> Review<'a> {
131      pub fn new(review: &'a patch::Review) -> Self {
132          Self(review)
133      }
134  
135      pub fn as_json(&self, aliases: &impl AliasStore) -> Value {
136          json!({
137              "id": self.0.id(),
138              "author": Author::new(self.0.author().id()).as_json(aliases),
139              "verdict": self.0.verdict(),
140              "summary": self.0.summary(),
141              "comments": self.0.comments().map(|(id, c)|
142                  thread::Comment::Patch(c).as_json(id, aliases)
143              ).collect::<Vec<_>>(),
144              "timestamp": self.0.timestamp().as_secs(),
145          })
146      }
147  }
148  
149  pub(crate) struct Experiment<'a>(&'a experiment::Experiment);
150  
151  impl<'a> Experiment<'a> {
152      pub fn new(experiment: &'a experiment::Experiment) -> Self {
153          Self(experiment)
154      }
155  
156      pub fn as_json(&self, id: experiment::ExperimentId, aliases: &impl AliasStore) -> Value {
157          let e = self.0;
158          json!({
159              "id": id.to_string(),
160              "author": Author::new(e.author.id()).as_json(aliases),
161              "description": e.description,
162              "parent": e.parent,
163              "baseCommit": e.base_commit,
164              "candidateRef": e.candidate_ref,
165              "candidateHead": e.candidate_head,
166              "touchedFiles": e.touched_files,
167              "linesAdded": e.lines_added,
168              "linesRemoved": e.lines_removed,
169              "harness": e.harness,
170              "command": e.command,
171              "metricName": e.metric_name,
172              "metricUnit": e.metric_unit,
173              "direction": e.direction,
174              "warmupRuns": e.warmup_runs,
175              "measurementRuns": e.measurement_runs,
176              "runnerClass": e.runner_class,
177              "os": e.os,
178              "cpu": e.cpu,
179              "baselineN": e.baseline_n,
180              "baselineMedianMilliunits": e.baseline_median_milliunits,
181              "candidateN": e.candidate_n,
182              "candidateMedianMilliunits": e.candidate_median_milliunits,
183              "deltaPctX100": e.delta_pct_x100,
184              "buildOk": e.build_ok,
185              "testsOk": e.tests_ok,
186              "sanitizersOk": e.sanitizers_ok,
187              "agentSystem": e.agent_system,
188              "agentModel": e.agent_model,
189              "reproductions": e.reproductions.iter().map(|r| json!({
190                  "verdict": r.verdict.to_string(),
191                  "runnerClass": r.runner_class,
192                  "baselineMedianMilliunits": r.baseline_median_milliunits,
193                  "candidateMedianMilliunits": r.candidate_median_milliunits,
194                  "deltaPctX100": r.delta_pct_x100,
195                  "author": Author::new(r.author.id()).as_json(aliases),
196                  "timestamp": r.timestamp.as_secs(),
197              })).collect::<Vec<_>>(),
198              "createdAt": e.created_at.as_secs(),
199          })
200      }
201  }
202  
203  fn get_refs(
204      repo: &git::Repository,
205      id: &cob::ActorId,
206      head: &radicle::git::Oid,
207  ) -> Result<Vec<radicle::git::fmt::RefString>, refs::Error> {
208      let remote = repo.remote(id)?;
209      let refs = remote
210          .refs
211          .iter()
212          .filter_map(|(name, o)| {
213              if o == head {
214                  Some(name.to_owned())
215              } else {
216                  None
217              }
218          })
219          .collect::<Vec<_>>();
220  
221      Ok(refs)
222  }