Browse Source

Rework issue body interaction to permit storing data

Currently this will break rather poorly if a HTML comment is embedded
into the JSON (as no escaping is attempted); we may eventually want to
pipe the JSON through some sort of HTML escaping but for now we'll deem
that insufficiently important to bother with.
Mark Rousskov 6 years ago
parent
commit
7eb203accc
2 changed files with 83 additions and 22 deletions
  1. 6 7
      src/handlers/assign.rs
  2. 77 15
      src/interactions.rs

+ 6 - 7
src/handlers/assign.rs

@@ -75,8 +75,8 @@ impl Handler for AssignmentHandler {
             }
         };
 
-        let e = EditIssueBody::new(&event.issue, "ASSIGN", String::new());
-        e.apply(&ctx.github)?;
+        let e = EditIssueBody::new(&event.issue, "ASSIGN");
+        e.apply(&ctx.github, String::new(), ())?;
 
         match event.issue.set_assignee(&ctx.github, &to_assign) {
             Ok(()) => return Ok(()), // we are done
@@ -85,15 +85,14 @@ impl Handler for AssignmentHandler {
                     .issue
                     .set_assignee(&ctx.github, &ctx.username)
                     .context("self-assignment failed")?;
-                let e = EditIssueBody::new(
-                    &event.issue,
-                    "ASSIGN",
+                e.apply(
+                    &ctx.github,
                     format!(
                         "This issue has been assigned to @{} via [this comment]({}).",
                         to_assign, event.comment.html_url
                     ),
-                );
-                e.apply(&ctx.github)?;
+                    (),
+                )?;
             }
             Err(e) => return Err(e.into()),
         }

+ 77 - 15
src/interactions.rs

@@ -33,39 +33,101 @@ impl<'a> ErrorComment<'a> {
 pub struct EditIssueBody<'a> {
     issue: &'a Issue,
     id: &'static str,
-    text: String,
 }
 
 static START_BOT: &str = "<!-- TRIAGEBOT_START -->\n\n----\n";
 static END_BOT: &str = "<!-- TRIAGEBOT_END -->";
 
 impl<'a> EditIssueBody<'a> {
-    pub fn new(issue: &'a Issue, id: &'static str, text: String) -> EditIssueBody<'a> {
-        EditIssueBody { issue, id, text }
+    pub fn new(issue: &'a Issue, id: &'static str) -> EditIssueBody<'a> {
+        EditIssueBody { issue, id }
     }
 
-    pub fn apply(&self, client: &GithubClient) -> Result<(), Error> {
+    fn get_current(&self) -> Option<&str> {
+        let start_section = self.start_section();
+        let end_section = self.end_section();
+        if self.issue.body.contains(START_BOT) {
+            if self.issue.body.contains(&start_section) {
+                let start_idx = self.issue.body.find(&start_section).unwrap();
+                let end_idx = self.issue.body.find(&end_section).unwrap();
+                Some(&self.issue.body[start_idx..(end_idx + end_section.len())])
+            } else {
+                None
+            }
+        } else {
+            None
+        }
+    }
+
+    pub fn current_data<T: serde::de::DeserializeOwned>(&self) -> Option<T> {
+        let all = self.get_current()?;
+        let start = self.data_section_start();
+        let end = self.data_section_end();
+        let start_idx = all.find(&start).unwrap();
+        let end_idx = all.find(&end).unwrap();
+        let text = &all[(start_idx + start.len())..end_idx];
+        Some(serde_json::from_str(text).unwrap_or_else(|e| {
+            panic!("deserializing data {:?} failed: {:?}", text, e);
+        }))
+    }
+
+    fn start_section(&self) -> String {
+        format!("<!-- TRIAGEBOT_{}_START -->\n", self.id)
+    }
+
+    fn end_section(&self) -> String {
+        format!("\n<!-- TRIAGEBOT_{}_END -->\n", self.id)
+    }
+
+    fn data_section_start(&self) -> String {
+        format!("\n<!-- TRIAGEBOT_{}_DATA_START$$", self.id)
+    }
+
+    fn data_section_end(&self) -> String {
+        format!("$$TRIAGEBOT_{}_DATA_END -->\n", self.id)
+    }
+
+    fn data_section<T>(&self, data: T) -> String
+    where
+        T: serde::Serialize,
+    {
+        format!(
+            "{}{}{}",
+            self.data_section_start(),
+            serde_json::to_string(&data).unwrap(),
+            self.data_section_end()
+        )
+    }
+
+    pub fn apply<T>(&self, client: &GithubClient, text: String, data: T) -> Result<(), Error>
+    where
+        T: serde::Serialize,
+    {
         let mut current_body = self.issue.body.clone();
-        let start_section = format!("<!-- TRIAGEBOT_{}_START -->\n", self.id);
-        let end_section = format!("\n<!-- TRIAGEBOT_{}_END -->\n", self.id);
+        let start_section = self.start_section();
+        let end_section = self.end_section();
 
-        let bot_section = format!("{}{}{}", start_section, self.text, end_section);
+        let bot_section = format!(
+            "{}{}{}{}",
+            start_section,
+            text,
+            self.data_section(data),
+            end_section
+        );
+        let empty_bot_section = format!("{}{}", start_section, end_section);
 
         let all_new = format!("\n\n{}{}{}", START_BOT, bot_section, END_BOT);
         if current_body.contains(START_BOT) {
             if current_body.contains(&start_section) {
                 let start_idx = current_body.find(&start_section).unwrap();
                 let end_idx = current_body.find(&end_section).unwrap();
-                let mut new = current_body.replace(
-                    &current_body[start_idx..(end_idx + end_section.len())],
-                    &bot_section,
-                );
-                if new.contains(&all_new) && self.text.is_empty() {
-                    let start_idx = new.find(&all_new).unwrap();
+                current_body.replace_range(start_idx..(end_idx + end_section.len()), &bot_section);
+                if current_body.contains(&all_new) && bot_section == empty_bot_section {
+                    let start_idx = current_body.find(&all_new).unwrap();
                     let end_idx = start_idx + all_new.len();
-                    new = new.replace(&new[start_idx..end_idx], "");
+                    current_body.replace_range(start_idx..end_idx, "");
                 }
-                self.issue.edit_body(&client, &new)?;
+                self.issue.edit_body(&client, &current_body)?;
             } else {
                 let end_idx = current_body.find(&END_BOT).unwrap();
                 current_body.insert_str(end_idx, &bot_section);