//! Discussion types: a per-repo forum next to issues. Storage lives in `store/discussions.rs`. //! Titles and bodies are checked with the issue validators, since the limits are the same. use crate::error::{Error, Result}; /// Categories a discussion can be filed under. The `as_str` names are stored. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DiscussionCategory { General, Ideas, Qa, /// Only Maintain or higher may post here. Announcements, } /// Every category, in the order the chooser shows them. pub const DISCUSSION_CATEGORIES: &[DiscussionCategory] = &[ DiscussionCategory::General, DiscussionCategory::Ideas, DiscussionCategory::Qa, DiscussionCategory::Announcements, ]; impl DiscussionCategory { /// The stored and URL name. pub fn as_str(self) -> &'static str { match self { DiscussionCategory::General => "general", DiscussionCategory::Ideas => "ideas", DiscussionCategory::Qa => "qa", DiscussionCategory::Announcements => "announcements", } } /// The label people read. pub fn label(self) -> &'static str { match self { DiscussionCategory::General => "General", DiscussionCategory::Ideas => "Ideas", DiscussionCategory::Qa => "Q&A", DiscussionCategory::Announcements => "Announcements", } } /// The category with this stored name, if any. pub fn parse(name: &str) -> Option { DISCUSSION_CATEGORIES .iter() .copied() .find(|c| c.as_str() == name) } } /// One discussion. `answer_comment_id` is set only in Q&A, and names the accepted reply. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Discussion { pub id: String, pub repo_id: String, pub number: i64, pub category: DiscussionCategory, pub title: String, pub body: String, pub author_id: String, pub answer_comment_id: Option, pub locked: bool, pub comment_count: i64, pub created_at: String, pub updated_at: String, } /// A discussion with its author's username, for the list. #[derive(Debug, Clone, PartialEq, Eq)] pub struct DiscussionListItem { pub discussion: Discussion, pub author: String, } /// Why a discussion is in a user's inbox. The `as_str` names are stored in `discussion_notifications`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DiscussionReason { /// The user was @mentioned in the discussion or one of its comments. Mention, /// Someone commented on the user's discussion. Reply, } impl DiscussionReason { /// The stored name. pub fn as_str(self) -> &'static str { match self { DiscussionReason::Mention => "mention", DiscussionReason::Reply => "reply", } } /// The reason with this stored name, if any. pub fn parse(name: &str) -> Option { match name { "mention" => Some(DiscussionReason::Mention), "reply" => Some(DiscussionReason::Reply), _ => None, } } } /// One inbox row for a discussion, joined with its repo and title for the notifications page. #[derive(Debug, Clone, PartialEq, Eq)] pub struct DiscussionNotification { pub discussion_id: String, pub repo_id: String, pub repo_owner: String, pub repo_name: String, pub number: i64, pub title: String, pub reason: DiscussionReason, /// Username of the last person who acted, if the account still exists. pub last_actor_name: Option, pub unread: bool, pub updated_at: String, } /// One comment. A top-level comment has no `parent_id`; a reply names a top-level comment of the /// same discussion. A deleted comment has `deleted` set and an empty body. #[derive(Debug, Clone, PartialEq, Eq)] pub struct DiscussionComment { pub id: String, pub discussion_id: String, pub parent_id: Option, pub author_id: String, pub body: String, pub created_at: String, pub updated_at: String, pub deleted: bool, } /// Filters and paging for the discussion list. `category: None` lists every category. #[derive(Debug, Clone, PartialEq, Eq)] pub struct DiscussionFilter { pub category: Option, pub page: u32, pub per_page: u32, } /// Validates a discussion title (trimmed, returned) and body. pub fn validate_discussion(title: &str, body: &str) -> Result { let title = crate::issues::validate_title(title)?; crate::issues::validate_body(body)?; Ok(title) } /// Validates a comment body: not blank, and within the body limit. pub fn validate_comment(body: &str) -> Result<()> { crate::issues::validate_body(body)?; if body.trim().is_empty() { return Err(Error::Invalid("comment is empty".into())); } Ok(()) }