Skip to main content

rung_github/
types.rs

1//! GitHub API types.
2
3use serde::{Deserialize, Serialize};
4
5/// A GitHub Pull Request.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct PullRequest {
8    /// PR number.
9    pub number: u64,
10
11    /// PR title.
12    pub title: String,
13
14    /// PR body/description.
15    pub body: Option<String>,
16
17    /// PR state.
18    pub state: PullRequestState,
19
20    /// Whether this is a draft PR.
21    pub draft: bool,
22
23    /// Head branch name.
24    pub head_branch: String,
25
26    /// Base branch name.
27    pub base_branch: String,
28
29    /// PR URL.
30    pub html_url: String,
31
32    /// Whether the PR is mergeable (None if GitHub is still computing).
33    pub mergeable: Option<bool>,
34
35    /// The mergeable state (e.g., "clean", "dirty", "blocked", "behind").
36    pub mergeable_state: Option<String>,
37}
38
39/// State of a pull request.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "lowercase")]
42pub enum PullRequestState {
43    /// PR is open.
44    Open,
45    /// PR was closed without merging.
46    Closed,
47    /// PR was merged.
48    Merged,
49}
50
51/// A CI check run.
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct CheckRun {
54    /// Check name.
55    pub name: String,
56
57    /// Check status.
58    pub status: CheckStatus,
59
60    /// URL to view check details.
61    pub details_url: Option<String>,
62}
63
64/// Status of a CI check.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(rename_all = "lowercase")]
67pub enum CheckStatus {
68    /// Check is queued.
69    Queued,
70    /// Check is in progress.
71    InProgress,
72    /// Check completed successfully.
73    Success,
74    /// Check failed.
75    Failure,
76    /// Check was skipped.
77    Skipped,
78    /// Check was cancelled.
79    Cancelled,
80}
81
82impl CheckStatus {
83    /// Check if this status indicates success.
84    #[must_use]
85    pub const fn is_success(&self) -> bool {
86        matches!(self, Self::Success | Self::Skipped)
87    }
88
89    /// Check if this status indicates failure.
90    #[must_use]
91    pub const fn is_failure(&self) -> bool {
92        matches!(self, Self::Failure)
93    }
94
95    /// Check if this status indicates the check is still running.
96    #[must_use]
97    pub const fn is_pending(&self) -> bool {
98        matches!(self, Self::Queued | Self::InProgress)
99    }
100}
101
102/// Request to create a pull request.
103#[derive(Debug, Serialize)]
104pub struct CreatePullRequest {
105    /// PR title.
106    pub title: String,
107
108    /// PR body.
109    pub body: String,
110
111    /// Head branch.
112    pub head: String,
113
114    /// Base branch.
115    pub base: String,
116
117    /// Whether to create as draft.
118    pub draft: bool,
119}
120
121/// Request to update a pull request.
122#[derive(Debug, Serialize)]
123pub struct UpdatePullRequest {
124    /// New title (optional).
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub title: Option<String>,
127
128    /// New body (optional).
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub body: Option<String>,
131
132    /// New base branch (optional).
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub base: Option<String>,
135}
136
137/// Method used to merge a pull request.
138#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
139#[serde(rename_all = "lowercase")]
140pub enum MergeMethod {
141    /// Create a merge commit.
142    Merge,
143    /// Squash all commits into one.
144    #[default]
145    Squash,
146    /// Rebase commits onto base.
147    Rebase,
148}
149
150/// Request to merge a pull request.
151#[derive(Debug, Serialize)]
152pub struct MergePullRequest {
153    /// Commit title (for squash/merge).
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub commit_title: Option<String>,
156
157    /// Commit message (for squash/merge).
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub commit_message: Option<String>,
160
161    /// Merge method.
162    pub merge_method: MergeMethod,
163}
164
165/// Result of merging a pull request.
166#[derive(Debug, Clone, Deserialize)]
167pub struct MergeResult {
168    /// SHA of the merge commit.
169    pub sha: String,
170
171    /// Whether the merge was successful.
172    pub merged: bool,
173
174    /// Message from the API.
175    pub message: String,
176}
177
178/// A comment on an issue or pull request.
179#[derive(Debug, Clone, Deserialize)]
180pub struct IssueComment {
181    /// Comment ID.
182    pub id: u64,
183
184    /// Comment body.
185    pub body: Option<String>,
186}
187
188/// Request to create an issue/PR comment.
189#[derive(Debug, Serialize)]
190pub struct CreateComment {
191    /// Comment body.
192    pub body: String,
193}
194
195/// Request to update an issue/PR comment.
196#[derive(Debug, Serialize)]
197pub struct UpdateComment {
198    /// New comment body.
199    pub body: String,
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_check_status_is_success() {
208        assert!(CheckStatus::Success.is_success());
209        assert!(CheckStatus::Skipped.is_success());
210        assert!(!CheckStatus::Failure.is_success());
211        assert!(!CheckStatus::Queued.is_success());
212        assert!(!CheckStatus::InProgress.is_success());
213        assert!(!CheckStatus::Cancelled.is_success());
214    }
215
216    #[test]
217    fn test_check_status_is_failure() {
218        assert!(CheckStatus::Failure.is_failure());
219        assert!(!CheckStatus::Success.is_failure());
220        assert!(!CheckStatus::Skipped.is_failure());
221        assert!(!CheckStatus::Queued.is_failure());
222        assert!(!CheckStatus::InProgress.is_failure());
223        assert!(!CheckStatus::Cancelled.is_failure());
224    }
225
226    #[test]
227    fn test_check_status_is_pending() {
228        assert!(CheckStatus::Queued.is_pending());
229        assert!(CheckStatus::InProgress.is_pending());
230        assert!(!CheckStatus::Success.is_pending());
231        assert!(!CheckStatus::Failure.is_pending());
232        assert!(!CheckStatus::Skipped.is_pending());
233        assert!(!CheckStatus::Cancelled.is_pending());
234    }
235
236    #[test]
237    fn test_pull_request_state_equality() {
238        assert_eq!(PullRequestState::Open, PullRequestState::Open);
239        assert_eq!(PullRequestState::Closed, PullRequestState::Closed);
240        assert_eq!(PullRequestState::Merged, PullRequestState::Merged);
241        assert_ne!(PullRequestState::Open, PullRequestState::Closed);
242    }
243
244    #[test]
245    fn test_merge_method_default() {
246        assert_eq!(MergeMethod::default(), MergeMethod::Squash);
247    }
248
249    #[test]
250    #[allow(clippy::unwrap_used)]
251    fn test_pull_request_state_serialization() {
252        assert_eq!(
253            serde_json::to_string(&PullRequestState::Open).unwrap(),
254            "\"open\""
255        );
256        assert_eq!(
257            serde_json::to_string(&PullRequestState::Closed).unwrap(),
258            "\"closed\""
259        );
260        assert_eq!(
261            serde_json::to_string(&PullRequestState::Merged).unwrap(),
262            "\"merged\""
263        );
264    }
265
266    #[test]
267    #[allow(clippy::unwrap_used)]
268    fn test_check_status_serialization() {
269        assert_eq!(
270            serde_json::to_string(&CheckStatus::Queued).unwrap(),
271            "\"queued\""
272        );
273        assert_eq!(
274            serde_json::to_string(&CheckStatus::InProgress).unwrap(),
275            "\"inprogress\""
276        );
277        assert_eq!(
278            serde_json::to_string(&CheckStatus::Success).unwrap(),
279            "\"success\""
280        );
281    }
282
283    #[test]
284    #[allow(clippy::unwrap_used)]
285    fn test_merge_method_serialization() {
286        assert_eq!(
287            serde_json::to_string(&MergeMethod::Merge).unwrap(),
288            "\"merge\""
289        );
290        assert_eq!(
291            serde_json::to_string(&MergeMethod::Squash).unwrap(),
292            "\"squash\""
293        );
294        assert_eq!(
295            serde_json::to_string(&MergeMethod::Rebase).unwrap(),
296            "\"rebase\""
297        );
298    }
299}