use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CommentTarget {
PullRequest {
repo_key: String,
number: u32,
},
Issue {
repo_key: String,
number: u32,
},
}
impl CommentTarget {
pub fn pull_request(repo_key: impl Into<String>, number: u32) -> Self {
Self::PullRequest {
repo_key: repo_key.into(),
number,
}
}
pub fn issue(repo_key: impl Into<String>, number: u32) -> Self {
Self::Issue {
repo_key: repo_key.into(),
number,
}
}
pub fn repo_key(&self) -> &str {
match self {
CommentTarget::PullRequest { repo_key, .. } => repo_key,
CommentTarget::Issue { repo_key, .. } => repo_key,
}
}
pub fn number(&self) -> u32 {
match self {
CommentTarget::PullRequest { number, .. } => *number,
CommentTarget::Issue { number, .. } => *number,
}
}
pub fn is_pull_request(&self) -> bool {
matches!(self, CommentTarget::PullRequest { .. })
}
pub fn is_issue(&self) -> bool {
matches!(self, CommentTarget::Issue { .. })
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comment {
pub id: u64,
pub target: CommentTarget,
pub author: String,
pub body: String,
pub created_at: u64,
pub updated_at: u64,
}
impl Comment {
pub fn new(
id: u64,
target: CommentTarget,
author: impl Into<String>,
body: impl Into<String>,
) -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
Self {
id,
target,
author: author.into(),
body: body.into(),
created_at: now,
updated_at: now,
}
}
pub fn update_body(&mut self, body: impl Into<String>) {
self.body = body.into();
self.updated_at = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
}
pub fn is_edited(&self) -> bool {
self.updated_at > self.created_at
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_comment_creation() {
let target = CommentTarget::pull_request("alice/repo", 1);
let comment = Comment::new(1, target.clone(), "bob_pubkey", "Great work!");
assert_eq!(comment.id, 1);
assert_eq!(comment.author, "bob_pubkey");
assert_eq!(comment.body, "Great work!");
assert!(!comment.is_edited());
assert!(target.is_pull_request());
}
#[test]
fn test_issue_comment() {
let target = CommentTarget::issue("alice/repo", 5);
let comment = Comment::new(
2,
target.clone(),
"carol_pubkey",
"I can reproduce this bug",
);
assert_eq!(comment.target.number(), 5);
assert!(target.is_issue());
assert!(!target.is_pull_request());
}
#[test]
fn test_comment_update() {
let target = CommentTarget::pull_request("alice/repo", 1);
let mut comment = Comment::new(1, target, "bob_pubkey", "Original text");
comment.created_at -= 1;
comment.update_body("Updated text");
assert_eq!(comment.body, "Updated text");
assert!(comment.is_edited());
}
#[test]
fn test_comment_target_helpers() {
let pr_target = CommentTarget::pull_request("alice/repo", 1);
assert_eq!(pr_target.repo_key(), "alice/repo");
assert_eq!(pr_target.number(), 1);
let issue_target = CommentTarget::issue("bob/project", 10);
assert_eq!(issue_target.repo_key(), "bob/project");
assert_eq!(issue_target.number(), 10);
}
}