cdx_core/extensions/collaboration/
thread.rs1use serde::{Deserialize, Serialize};
4
5use super::Comment;
6
7#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct CommentThread {
11 pub comments: Vec<Comment>,
13}
14
15impl CommentThread {
16 #[must_use]
18 pub fn new() -> Self {
19 Self {
20 comments: Vec::new(),
21 }
22 }
23
24 pub fn add(&mut self, comment: Comment) {
26 self.comments.push(comment);
27 }
28
29 #[must_use]
31 pub fn get(&self, id: &str) -> Option<&Comment> {
32 Self::find_comment(id, &self.comments)
33 }
34
35 #[must_use]
37 pub fn get_mut(&mut self, id: &str) -> Option<&mut Comment> {
38 self.find_comment_mut(id)
39 }
40
41 #[must_use]
43 pub fn for_block(&self, block_ref: &str) -> Vec<&Comment> {
44 self.comments
45 .iter()
46 .filter(|c| c.block_ref == block_ref)
47 .collect()
48 }
49
50 #[must_use]
52 pub fn unresolved(&self) -> Vec<&Comment> {
53 self.comments.iter().filter(|c| !c.resolved).collect()
54 }
55
56 #[must_use]
58 pub fn resolved(&self) -> Vec<&Comment> {
59 self.comments.iter().filter(|c| c.resolved).collect()
60 }
61
62 #[must_use]
64 pub fn len(&self) -> usize {
65 self.comments.len()
66 }
67
68 #[must_use]
70 pub fn is_empty(&self) -> bool {
71 self.comments.is_empty()
72 }
73
74 fn find_comment<'a>(id: &str, comments: &'a [Comment]) -> Option<&'a Comment> {
76 for comment in comments {
77 if comment.id == id {
78 return Some(comment);
79 }
80 if let Some(found) = Self::find_comment(id, &comment.replies) {
81 return Some(found);
82 }
83 }
84 None
85 }
86
87 fn find_comment_mut(&mut self, id: &str) -> Option<&mut Comment> {
89 Self::find_comment_mut_recursive(&mut self.comments, id)
90 }
91
92 fn find_comment_mut_recursive<'a>(
94 comments: &'a mut [Comment],
95 id: &str,
96 ) -> Option<&'a mut Comment> {
97 for comment in comments {
98 if comment.id == id {
99 return Some(comment);
100 }
101 if let Some(found) = Self::find_comment_mut_recursive(&mut comment.replies, id) {
102 return Some(found);
103 }
104 }
105 None
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::extensions::collaboration::Collaborator;
113
114 #[test]
115 fn test_get_mut_finds_reply() {
116 let mut thread = CommentThread::new();
117 let author = Collaborator::new("Alice");
118
119 let mut parent = Comment::new("c1", "block-1", author.clone(), "Parent comment");
120 let reply = Comment::new("reply-1", "block-1", author, "Reply to parent");
121 parent.replies.push(reply);
122
123 thread.add(parent);
124
125 assert!(
127 thread.get_mut("reply-1").is_some(),
128 "get_mut should find nested replies"
129 );
130
131 if let Some(reply) = thread.get_mut("reply-1") {
133 reply.resolved = true;
134 }
135
136 let reply = thread.get("reply-1").unwrap();
138 assert!(reply.resolved);
139 }
140
141 #[test]
142 fn test_get_mut_returns_none_for_missing() {
143 let mut thread = CommentThread::new();
144 assert!(thread.get_mut("nonexistent").is_none());
145 }
146}