Basic Issue data structures
parent: tbd commit: 509aa4e
Showing 6 changed files with 299 insertions and 15 deletions
giterated-models/src/issue/events.rs
@@ -0,0 +1,38 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::user::User; | |
4 | ||
5 | use super::{IssueComment, IssueCommentRevision, IssueStatus, IssueTag}; | |
6 | ||
7 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
8 | pub struct IssueTimeline { | |
9 | pub events: Vec<IssueEvent>, | |
10 | } | |
11 | ||
12 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
13 | pub struct IssueEvent { | |
14 | /// The id of this event | |
15 | pub id: u64, | |
16 | /// Time at which the event was created | |
17 | pub created: chrono::DateTime<chrono::Utc>, | |
18 | /// User who triggered the event | |
19 | pub triggeror: User, | |
20 | /// Content of the event | |
21 | pub content: IssueEventContent, | |
22 | } | |
23 | ||
24 | // TODO: serialize with flattened internal struct | |
25 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
26 | pub enum IssueEventContent { | |
27 | CommentCreated(IssueComment), | |
28 | CommentRevised(IssueCommentRevision), | |
29 | CommentDeleted(IssueComment), | |
30 | ||
31 | Tagged(IssueTag), | |
32 | Untagged(IssueTag), | |
33 | ||
34 | StatusChanged(IssueStatus), | |
35 | ||
36 | UserAssigned(User), | |
37 | UserUnassigned(User), | |
38 | } |
giterated-models/src/issue/mod.rs
@@ -0,0 +1,139 @@ | ||
1 | pub mod events; | |
2 | pub mod operations; | |
3 | pub mod values; | |
4 | ||
5 | use std::{fmt::Display, str::FromStr}; | |
6 | ||
7 | use events::IssueTimeline; | |
8 | use serde::{Deserialize, Serialize}; | |
9 | ||
10 | use crate::{object::GiteratedObject, repository::Repository, user::User}; | |
11 | ||
12 | /// An issue, defined by the [`Repository`] it is related to along with | |
13 | /// its id. | |
14 | /// | |
15 | /// # Textual Format | |
16 | /// An issue's textual reference is defined as: | |
17 | /// | |
18 | /// `#{id: u64}:{repository: Repository}` | |
19 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
20 | pub struct Issue { | |
21 | pub id: u64, | |
22 | /// The repository the issue is related to | |
23 | pub repository: Repository, | |
24 | } | |
25 | ||
26 | impl GiteratedObject for Issue { | |
27 | fn object_name() -> &'static str { | |
28 | "issue" | |
29 | } | |
30 | ||
31 | fn home_uri(&self) -> String { | |
32 | self.repository.home_uri() | |
33 | } | |
34 | ||
35 | fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> { | |
36 | Ok(Issue::from_str(object_str)?) | |
37 | } | |
38 | } | |
39 | ||
40 | impl Display for Issue { | |
41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
42 | f.write_str(&format!("#{}:{}", self.id, self.repository)) | |
43 | } | |
44 | } | |
45 | ||
46 | #[derive(Debug, thiserror::Error)] | |
47 | #[error("couldn't parse issue")] | |
48 | pub struct IssueParseError; | |
49 | ||
50 | impl FromStr for Issue { | |
51 | type Err = IssueParseError; | |
52 | ||
53 | fn from_str(s: &str) -> Result<Self, Self::Err> { | |
54 | // Remove the pound from the start | |
55 | let s = s | |
56 | .starts_with('#') | |
57 | .then(|| s.replace('#', "")) | |
58 | .ok_or(IssueParseError)?; | |
59 | // Split the id from the repository | |
60 | let (id, repository) = s.split_once(':').ok_or(IssueParseError)?; | |
61 | ||
62 | // Parse the id and the repository | |
63 | let id: u64 = id.parse().map_err(|_| IssueParseError)?; | |
64 | let repository = Repository::from_str(repository).map_err(|_| IssueParseError)?; | |
65 | ||
66 | Ok(Self { id, repository }) | |
67 | } | |
68 | } | |
69 | ||
70 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
71 | pub struct IssueInfo { | |
72 | #[serde(flatten)] | |
73 | pub basic: IssueInfoBasic, | |
74 | pub body: String, | |
75 | pub timeline: IssueTimeline, | |
76 | } | |
77 | ||
78 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
79 | pub struct IssueInfoBasic { | |
80 | pub id: u64, | |
81 | pub status: IssueStatus, | |
82 | pub title: String, | |
83 | pub tags: Vec<IssueTag>, | |
84 | pub last_activity: chrono::DateTime<chrono::Utc>, | |
85 | pub created: chrono::DateTime<chrono::Utc>, | |
86 | pub creator: User, | |
87 | pub statistics: IssueStatistics, | |
88 | pub assignees: Vec<User>, | |
89 | } | |
90 | ||
91 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
92 | pub struct IssueStatistics { | |
93 | pub comments: u64, | |
94 | } | |
95 | ||
96 | /// Tag (sometimes referred to as label) to categorize issues. | |
97 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
98 | pub struct IssueTag { | |
99 | pub category: String, | |
100 | pub sub_category: Option<String>, | |
101 | pub description: Option<String>, | |
102 | } | |
103 | ||
104 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
105 | pub enum IssueStatus { | |
106 | Open, | |
107 | Closed, | |
108 | } | |
109 | ||
110 | impl Display for IssueStatus { | |
111 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
112 | match self { | |
113 | IssueStatus::Open => f.write_str("Open"), | |
114 | IssueStatus::Closed => f.write_str("Closed"), | |
115 | } | |
116 | } | |
117 | } | |
118 | ||
119 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
120 | pub struct IssueComment { | |
121 | /// Event id | |
122 | pub id: u64, | |
123 | /// Creation date | |
124 | pub created: chrono::DateTime<chrono::Utc>, | |
125 | /// The user who created the comment | |
126 | pub creator: User, | |
127 | /// Body of the comment | |
128 | pub body: String, | |
129 | } | |
130 | ||
131 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
132 | pub struct IssueCommentRevision { | |
133 | /// Event id | |
134 | pub id: u64, | |
135 | /// Creation date | |
136 | pub created: chrono::DateTime<chrono::Utc>, | |
137 | /// Body of the comment | |
138 | pub body: String, | |
139 | } |
giterated-models/src/issue/operations.rs
@@ -0,0 +1,40 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{ | |
4 | error::{OperationError, RepositoryError}, | |
5 | object::Object, | |
6 | object_backend::ObjectBackend, | |
7 | operation::GiteratedOperation, | |
8 | }; | |
9 | ||
10 | use super::{Issue, IssueInfo}; | |
11 | ||
12 | /// A request to get info about an issue. (graphql please) | |
13 | /// | |
14 | /// # Authentication | |
15 | /// - Instance Authentication | |
16 | /// - Validate request against the `issued_for` public key | |
17 | /// - Validate User token against the user's instance's public key | |
18 | /// # Authorization | |
19 | /// - User Authorization | |
20 | /// - Potential User permissions checks | |
21 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
22 | pub struct IssueInfoRequest { | |
23 | pub id: u64, | |
24 | } | |
25 | ||
26 | impl GiteratedOperation<Issue> for IssueInfoRequest { | |
27 | type Success = IssueInfo; | |
28 | type Failure = RepositoryError; | |
29 | } | |
30 | ||
31 | impl<S: Clone + Send + Sync, B: ObjectBackend<S> + std::fmt::Debug> Object<'_, S, Issue, B> { | |
32 | pub async fn info( | |
33 | &mut self, | |
34 | id: u64, | |
35 | operation_state: &S, | |
36 | ) -> Result<IssueInfo, OperationError<RepositoryError>> { | |
37 | self.request::<IssueInfoRequest>(IssueInfoRequest { id }, operation_state) | |
38 | .await | |
39 | } | |
40 | } |
giterated-models/src/issue/values.rs
@@ -0,0 +1,81 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{settings::Setting, user::User, value::GiteratedObjectValue}; | |
4 | ||
5 | use super::{Issue, IssueStatus, IssueTag}; | |
6 | ||
7 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
8 | #[repr(transparent)] | |
9 | #[serde(transparent)] | |
10 | pub struct Tags(pub Vec<IssueTag>); | |
11 | ||
12 | impl GiteratedObjectValue for Tags { | |
13 | type Object = Issue; | |
14 | ||
15 | fn value_name() -> &'static str { | |
16 | "tags" | |
17 | } | |
18 | } | |
19 | ||
20 | impl Setting for Tags { | |
21 | fn name() -> &'static str { | |
22 | "tags" | |
23 | } | |
24 | } | |
25 | ||
26 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
27 | #[repr(transparent)] | |
28 | #[serde(transparent)] | |
29 | pub struct Status(pub IssueStatus); | |
30 | ||
31 | impl GiteratedObjectValue for Status { | |
32 | type Object = Issue; | |
33 | ||
34 | fn value_name() -> &'static str { | |
35 | "status" | |
36 | } | |
37 | } | |
38 | ||
39 | impl Setting for Status { | |
40 | fn name() -> &'static str { | |
41 | "status" | |
42 | } | |
43 | } | |
44 | ||
45 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
46 | #[repr(transparent)] | |
47 | #[serde(transparent)] | |
48 | pub struct Title(pub String); | |
49 | ||
50 | impl GiteratedObjectValue for Title { | |
51 | type Object = Issue; | |
52 | ||
53 | fn value_name() -> &'static str { | |
54 | "title" | |
55 | } | |
56 | } | |
57 | ||
58 | impl Setting for Title { | |
59 | fn name() -> &'static str { | |
60 | "title" | |
61 | } | |
62 | } | |
63 | ||
64 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
65 | #[repr(transparent)] | |
66 | #[serde(transparent)] | |
67 | pub struct Assignees(pub Vec<User>); | |
68 | ||
69 | impl GiteratedObjectValue for Assignees { | |
70 | type Object = Issue; | |
71 | ||
72 | fn value_name() -> &'static str { | |
73 | "assignees" | |
74 | } | |
75 | } | |
76 | ||
77 | impl Setting for Assignees { | |
78 | fn name() -> &'static str { | |
79 | "assignees" | |
80 | } | |
81 | } |
giterated-models/src/lib.rs
@@ -3,6 +3,7 @@ pub mod discovery; | ||
3 | 3 | pub mod error; |
4 | 4 | pub mod handshake; |
5 | 5 | pub mod instance; |
6 | pub mod issue; | |
6 | 7 | pub mod message; |
7 | 8 | pub mod object; |
8 | 9 | pub mod object_backend; |
giterated-models/src/repository/mod.rs
@@ -472,18 +472,3 @@ pub struct RepositorySummary { | ||
472 | 472 | pub description: Option<String>, |
473 | 473 | pub last_commit: Option<Commit>, |
474 | 474 | } |
475 | ||
476 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
477 | pub struct IssueLabel { | |
478 | pub name: String, | |
479 | pub color: String, | |
480 | } | |
481 | ||
482 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
483 | pub struct RepositoryIssue { | |
484 | pub author: User, | |
485 | pub id: u64, | |
486 | pub title: String, | |
487 | pub contents: String, | |
488 | pub labels: Vec<IssueLabel>, | |
489 | } |