//! Issue, comment, label and milestone types, and their validation. Stored shapes are in //! migrations/0004_issues.sql; the store functions live in store/issues.rs and store/labels.rs. use crate::error::{Error, Result}; use crate::model::Id; /// Longest accepted issue title, in characters. pub const MAX_TITLE: usize = 256; /// Largest accepted issue, comment or edit body, in bytes. pub const MAX_BODY: usize = 65536; /// Longest accepted label name, in characters. pub const MAX_LABEL_NAME: usize = 50; /// Most assignees one issue can have. pub const MAX_ASSIGNEES: usize = 10; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IssueState { Open, Closed, } impl IssueState { pub fn as_str(&self) -> &'static str { match self { IssueState::Open => "open", IssueState::Closed => "closed", } } } impl std::str::FromStr for IssueState { type Err = Error; fn from_str(s: &str) -> Result { match s { "open" => Ok(IssueState::Open), "closed" => Ok(IssueState::Closed), other => Err(Error::Invalid(format!("issue state {other:?}"))), } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StateReason { Completed, NotPlanned, } impl StateReason { pub fn as_str(&self) -> &'static str { match self { StateReason::Completed => "completed", StateReason::NotPlanned => "not_planned", } } } impl std::str::FromStr for StateReason { type Err = Error; fn from_str(s: &str) -> Result { match s { "completed" => Ok(StateReason::Completed), "not_planned" => Ok(StateReason::NotPlanned), other => Err(Error::Invalid(format!("state reason {other:?}"))), } } } #[derive(Debug, Clone, PartialEq)] pub struct Issue { pub id: Id, pub repo_id: Id, pub number: i64, pub title: String, pub body: String, pub state: IssueState, pub state_reason: Option, pub author_id: Id, pub milestone_id: Option, pub comment_count: i64, pub created_at: String, pub updated_at: String, pub closed_at: Option, /// Locked conversations accept writes only from collaborators with Triage or higher. pub locked: bool, /// One of `LOCK_REASONS`, or `None` when no reason was given. pub lock_reason: Option, } /// Reasons accepted when locking a conversation. pub const LOCK_REASONS: &[&str] = &["off_topic", "too_heated", "resolved", "spam"]; #[derive(Debug, Clone, PartialEq)] pub struct Comment { pub id: Id, pub issue_id: Id, pub author_id: Id, pub body: String, pub created_at: String, pub updated_at: String, pub edited: bool, } #[derive(Debug, Clone, PartialEq)] pub struct IssueEvent { pub id: Id, pub issue_id: Id, /// `None` for system events. pub actor_id: Option, pub kind: String, pub data: serde_json::Value, pub created_at: String, } #[derive(Debug, Clone, PartialEq)] pub struct Label { pub id: Id, pub repo_id: Id, pub name: String, /// Lowercase `#rrggbb`. pub color: String, pub description: String, } #[derive(Debug, Clone, PartialEq)] pub struct Milestone { pub id: Id, pub repo_id: Id, pub title: String, pub description: String, /// `YYYY-MM-DD` or `None`. pub due_on: Option, pub state: IssueState, pub created_at: String, pub closed_at: Option, /// Counts filled by list/get queries. pub open_issues: i64, pub closed_issues: i64, } #[derive(Debug, Clone, PartialEq)] pub struct IssueFilter { pub state: Option, /// Label name. pub label: Option, /// Milestone title. pub milestone: Option, /// Author username. pub author: Option, /// Assignee username. pub assignee: Option, /// Substring of the title, case-insensitive. pub query: Option, pub sort: IssueSort, /// 1-based. pub page: u32, pub per_page: u32, } impl Default for IssueFilter { fn default() -> Self { IssueFilter { state: Some(IssueState::Open), label: None, milestone: None, author: None, assignee: None, query: None, sort: IssueSort::Newest, page: 1, per_page: 25, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum IssueSort { #[default] Newest, Oldest, RecentlyUpdated, } impl IssueSort { /// The value used in the `sort` query parameter. pub fn as_str(&self) -> &'static str { match self { IssueSort::Newest => "newest", IssueSort::Oldest => "oldest", IssueSort::RecentlyUpdated => "updated", } } } impl std::str::FromStr for IssueSort { type Err = Error; fn from_str(s: &str) -> Result { match s { "newest" => Ok(IssueSort::Newest), "oldest" => Ok(IssueSort::Oldest), "updated" => Ok(IssueSort::RecentlyUpdated), other => Err(Error::Invalid(format!("sort {other:?}"))), } } } #[derive(Debug, Clone, PartialEq)] pub struct IssueListItem { pub issue: Issue, /// Display username of the author. pub author: String, pub labels: Vec