forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
4.8 KiB
1//! Discussion types: a per-repo forum next to issues. Storage lives in `store/discussions.rs`.
2//! Titles and bodies are checked with the issue validators, since the limits are the same.
3
4use crate::error::{Error, Result};
5
6/// Categories a discussion can be filed under. The `as_str` names are stored.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum DiscussionCategory {
9 General,
10 Ideas,
11 Qa,
12 /// Only Maintain or higher may post here.
13 Announcements,
14}
15
16/// Every category, in the order the chooser shows them.
17pub const DISCUSSION_CATEGORIES: &[DiscussionCategory] = &[
18 DiscussionCategory::General,
19 DiscussionCategory::Ideas,
20 DiscussionCategory::Qa,
21 DiscussionCategory::Announcements,
22];
23
24impl DiscussionCategory {
25 /// The stored and URL name.
26 pub fn as_str(self) -> &'static str {
27 match self {
28 DiscussionCategory::General => "general",
29 DiscussionCategory::Ideas => "ideas",
30 DiscussionCategory::Qa => "qa",
31 DiscussionCategory::Announcements => "announcements",
32 }
33 }
34
35 /// The label people read.
36 pub fn label(self) -> &'static str {
37 match self {
38 DiscussionCategory::General => "General",
39 DiscussionCategory::Ideas => "Ideas",
40 DiscussionCategory::Qa => "Q&A",
41 DiscussionCategory::Announcements => "Announcements",
42 }
43 }
44
45 /// The category with this stored name, if any.
46 pub fn parse(name: &str) -> Option<DiscussionCategory> {
47 DISCUSSION_CATEGORIES
48 .iter()
49 .copied()
50 .find(|c| c.as_str() == name)
51 }
52}
53
54/// One discussion. `answer_comment_id` is set only in Q&A, and names the accepted reply.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct Discussion {
57 pub id: String,
58 pub repo_id: String,
59 pub number: i64,
60 pub category: DiscussionCategory,
61 pub title: String,
62 pub body: String,
63 pub author_id: String,
64 pub answer_comment_id: Option<String>,
65 pub locked: bool,
66 pub comment_count: i64,
67 pub created_at: String,
68 pub updated_at: String,
69}
70
71/// A discussion with its author's username, for the list.
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct DiscussionListItem {
74 pub discussion: Discussion,
75 pub author: String,
76}
77
78/// Why a discussion is in a user's inbox. The `as_str` names are stored in `discussion_notifications`.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum DiscussionReason {
81 /// The user was @mentioned in the discussion or one of its comments.
82 Mention,
83 /// Someone commented on the user's discussion.
84 Reply,
85}
86
87impl DiscussionReason {
88 /// The stored name.
89 pub fn as_str(self) -> &'static str {
90 match self {
91 DiscussionReason::Mention => "mention",
92 DiscussionReason::Reply => "reply",
93 }
94 }
95
96 /// The reason with this stored name, if any.
97 pub fn parse(name: &str) -> Option<DiscussionReason> {
98 match name {
99 "mention" => Some(DiscussionReason::Mention),
100 "reply" => Some(DiscussionReason::Reply),
101 _ => None,
102 }
103 }
104}
105
106/// One inbox row for a discussion, joined with its repo and title for the notifications page.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct DiscussionNotification {
109 pub discussion_id: String,
110 pub repo_id: String,
111 pub repo_owner: String,
112 pub repo_name: String,
113 pub number: i64,
114 pub title: String,
115 pub reason: DiscussionReason,
116 /// Username of the last person who acted, if the account still exists.
117 pub last_actor_name: Option<String>,
118 pub unread: bool,
119 pub updated_at: String,
120}
121
122/// One comment. A top-level comment has no `parent_id`; a reply names a top-level comment of the
123/// same discussion. A deleted comment has `deleted` set and an empty body.
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct DiscussionComment {
126 pub id: String,
127 pub discussion_id: String,
128 pub parent_id: Option<String>,
129 pub author_id: String,
130 pub body: String,
131 pub created_at: String,
132 pub updated_at: String,
133 pub deleted: bool,
134}
135
136/// Filters and paging for the discussion list. `category: None` lists every category.
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct DiscussionFilter {
139 pub category: Option<DiscussionCategory>,
140 pub page: u32,
141 pub per_page: u32,
142}
143
144/// Validates a discussion title (trimmed, returned) and body.
145pub fn validate_discussion(title: &str, body: &str) -> Result<String> {
146 let title = crate::issues::validate_title(title)?;
147 crate::issues::validate_body(body)?;
148 Ok(title)
149}
150
151/// Validates a comment body: not blank, and within the body limit.
152pub fn validate_comment(body: &str) -> Result<()> {
153 crate::issues::validate_body(body)?;
154 if body.trim().is_empty() {
155 return Err(Error::Invalid("comment is empty".into()));
156 }
157 Ok(())
158}