1use serde::Deserialize;
2
3#[derive(Deserialize, Debug)]
4pub struct ChangeLogItem {
5 pub field: String,
6 #[serde(rename = "fieldId")]
7 pub field_id: String,
8 pub fieldtype: String,
9 pub from: Option<String>,
10 #[serde(rename = "fromString")]
11 pub from_string: Option<String>,
12 pub to: Option<String>,
13 #[serde(rename = "toString")]
14 pub to_string: Option<String>,
15}
16
17#[derive(Deserialize, Debug)]
18pub struct ChangeLog {
19 pub items: Vec<ChangeLogItem>,
20}
21
22#[derive(Deserialize, Debug)]
23pub struct AvatarUrls {
24 #[serde(rename = "16x16")]
25 _16x16: String,
26 #[serde(rename = "24x24")]
27 _24x24: String,
28 #[serde(rename = "32x32")]
29 _32x32: String,
30 #[serde(rename = "48x48")]
31 _48x48: String,
32}
33
34#[derive(Deserialize, Debug)]
35pub struct User {
36 #[serde(rename = "accountType")]
37 pub account_type: String,
38 pub active: bool,
39 #[serde(rename = "avatarUrls")]
40 pub avatar_urls: AvatarUrls,
41 #[serde(rename = "displayName")]
42 pub display_name: String,
43 #[serde(rename = "self")]
44 pub _self: String,
45 #[serde(rename = "timeZone")]
46 pub time_zone: String,
47}
48
49#[derive(Deserialize, Debug)]
50pub struct Project {
51 #[serde(rename = "avatarUrls")]
52 pub avatar_urls: AvatarUrls,
53 pub id: String,
54 pub key: String,
55 pub name: String,
56 #[serde(rename = "projectTypeKey")]
57 pub project_type_key: String,
58 #[serde(rename = "self")]
59 pub _self: String,
60 pub simplified: bool,
61}
62
63#[derive(Deserialize, Debug)]
64pub struct Progress {
65 pub progress: u64,
66 pub total: u64,
67}
68
69#[derive(Deserialize, Debug)]
70pub struct IssueType {
71 pub description: String,
72 pub name: String,
73 #[serde(rename = "self")]
74 pub _self: String,
75 pub subtask: bool,
76}
77
78#[derive(Deserialize, Debug)]
79pub struct Votes {
80 #[serde(rename = "hasVoted")]
81 pub has_voted: bool,
82 #[serde(rename = "self")]
83 pub _self: String,
84 pub votes: u64,
85}
86
87#[derive(Deserialize, Debug)]
88pub struct IssueField {
89 pub aggregateprogress: Option<Progress>,
90 pub assignee: Option<User>,
91 pub created: Option<String>,
92 pub creator: Option<User>,
93 #[serde(default = "Vec::new")]
94 pub labels: Vec<String>,
95 #[serde(rename = "lastViewed")]
96 pub last_viewed: Option<String>,
97 pub progress: Option<Progress>,
98 pub project: Project,
99 pub reporter: Option<User>,
100 pub summary: String,
101 pub updated: Option<String>,
102 pub votes: Option<Votes>,
103}
104
105#[derive(Deserialize, Debug)]
106pub struct Issue {
107 pub fields: IssueField,
108 pub id: String,
109 pub key: String,
110 #[serde(rename = "self")]
111 pub _self: String,
112}
113
114#[derive(Deserialize, Debug)]
115pub struct Comment {
116 pub author: User,
117 pub body: String,
118 pub created: String,
119 #[serde(rename = "self")]
120 pub _self: String,
121 #[serde(rename = "updateAuthor")]
122 pub update_author: User,
123 pub updated: String,
124}
125
126#[derive(Deserialize, Debug)]
127pub struct InboundData {
128 pub changelog: Option<ChangeLog>,
129 pub issue: Option<Issue>,
130 pub comment: Option<Comment>,
131 pub timestamp: u64,
132 #[serde(rename = "webhookEvent")]
133 pub webhook_event: String,
134}
135
136impl InboundData {
137 #[inline]
138 pub fn get_changelog(&self) -> Result<&ChangeLog, String> {
139 self.changelog
140 .as_ref()
141 .ok_or("Missing changelog".to_string())
142 }
143
144 #[inline]
145 pub fn get_issue(&self) -> Result<&Issue, String> {
146 self.issue.as_ref().ok_or("Missing issue".to_string())
147 }
148
149 #[inline]
150 pub fn get_comment(&self) -> Result<&Comment, String> {
151 self.comment.as_ref().ok_or("Missing comment".to_string())
152 }
153}
154
155pub fn inbound(s: String) -> Result<InboundData, String> {
157 #[cfg(debug_assertions)]
158 return serde_json::from_str::<InboundData>(&s)
159 .map_err(|e| format!("Parsing Jira Webhook payload failed: {}", e.to_string()));
160
161 #[cfg(not(debug_assertions))]
162 serde_json::from_str::<InboundData>(&s)
163 .map_err(|_| format!("Parsing Jira Webhook payload failed: {}", s))
164}
165
166pub mod outbound {
167 use std::collections::HashMap;
168
169 use serde_json::{json, Value};
170
171 pub struct OutboundData<'a> {
172 inner: HashMap<&'a str, Value>,
173 }
174
175 impl<'a> OutboundData<'a> {
176 pub fn summary<S: Into<String>>(mut self, summary: S) -> OutboundData<'a> {
178 self.inner.insert("summary", json!(summary.into()));
179 self
180 }
181
182 pub fn description<S: Into<String>>(mut self, description: S) -> OutboundData<'a> {
184 self.inner.insert("description", json!(description.into()));
185 self
186 }
187
188 pub fn transition<S: Into<String>>(mut self, transition: S) -> OutboundData<'a> {
190 self.inner.insert("transition", json!(transition.into()));
191 self
192 }
193
194 pub fn build(self) -> Result<String, String> {
196 if self.inner.is_empty() {
197 return Err("OutboundData build failed: empty field".to_string());
198 }
199
200 serde_json::to_string(&self.inner)
201 .map_err(|e| format!("OutboundData build failed: {}", e.to_string()))
202 }
203 }
204
205 pub fn create_issue<'a, S: Into<String>>(summary: S) -> OutboundData<'a> {
207 OutboundData {
208 inner: [("summary", json!(summary.into()))]
209 .into_iter()
210 .collect::<HashMap<&str, Value>>(),
211 }
212 }
213
214 pub fn modify_issue<'a, S: Into<String>>(issue_key: S) -> OutboundData<'a> {
216 OutboundData {
217 inner: [("issue_key", json!(issue_key.into()))]
218 .into_iter()
219 .collect::<HashMap<&str, Value>>(),
220 }
221 }
222}