Skip to main content

jira_core/model/
issue.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use super::attachment::Attachment;
7use super::field::FieldValue;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Issue {
11    pub id: String,
12    pub key: String,
13    pub summary: String,
14    /// ADF (Atlassian Document Format) JSON
15    pub description: Option<Value>,
16    pub status: String,
17    pub assignee: Option<String>,
18    pub reporter: Option<String>,
19    pub priority: Option<String>,
20    pub issue_type: String,
21    pub project_key: String,
22    pub created: String,
23    pub updated: String,
24    /// Attachments on this issue
25    pub attachments: Vec<Attachment>,
26    /// Issue links (blocks, relates, etc.)
27    pub links: Vec<super::link::IssueLink>,
28    /// Raw fields map for custom fields
29    pub fields: Value,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, Default)]
33pub struct CreateIssueRequest {
34    pub project_key: String,
35    pub summary: String,
36    pub description: Option<String>,
37    pub issue_type: String,
38    pub assignee: Option<String>,
39    pub priority: Option<String>,
40}
41
42/// Extended create request with dynamic custom fields.
43#[derive(Debug, Clone, Default)]
44pub struct CreateIssueRequestV2 {
45    pub project_key: String,
46    pub summary: String,
47    /// Markdown description — converted to ADF automatically. Ignored if `description_adf` is set.
48    pub description: Option<String>,
49    /// Pre-built ADF description — takes priority over `description`.
50    pub description_adf: Option<Value>,
51    pub issue_type: String,
52    pub assignee: Option<String>,
53    pub priority: Option<String>,
54    /// Labels (plain string list, e.g. ["bug", "backend"])
55    pub labels: Vec<String>,
56    /// Component names (e.g. ["auth", "api"])
57    pub components: Vec<String>,
58    /// Parent issue key for sub-tasks (e.g. "PROJ-100")
59    pub parent: Option<String>,
60    /// Fix version names (e.g. ["v1.0", "v1.1"])
61    pub fix_versions: Vec<String>,
62    /// Custom field ID → typed value
63    pub custom_fields: HashMap<String, FieldValue>,
64}
65
66#[derive(Debug, Clone, Default)]
67pub struct UpdateIssueRequest {
68    pub summary: Option<String>,
69    /// Markdown description — converted to ADF. Ignored if `description_adf` is set.
70    pub description: Option<String>,
71    /// Pre-built ADF description — takes priority over `description`.
72    pub description_adf: Option<Value>,
73    pub assignee: Option<String>,
74    pub priority: Option<String>,
75    pub status: Option<String>,
76    /// Labels (replaces existing labels)
77    pub labels: Option<Vec<String>>,
78    /// Component names (replaces existing components)
79    pub components: Option<Vec<String>>,
80    /// Fix version names (replaces existing fix versions)
81    pub fix_versions: Option<Vec<String>>,
82    /// Parent issue key
83    pub parent: Option<String>,
84    /// Arbitrary custom field values (field_id → value)
85    pub custom_fields: HashMap<String, FieldValue>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct SearchResult {
90    pub issues: Vec<Issue>,
91    pub next_page_token: Option<String>,
92    pub total: Option<u64>,
93}
94
95/// Raw Jira API issue response — used internally for deserialization
96#[derive(Debug, Deserialize)]
97pub(crate) struct RawIssue {
98    pub id: String,
99    pub key: String,
100    pub fields: Value,
101}
102
103impl RawIssue {
104    pub fn into_issue(self) -> Issue {
105        let fields = &self.fields;
106
107        let summary = fields
108            .get("summary")
109            .and_then(|v| v.as_str())
110            .unwrap_or("")
111            .to_string();
112
113        let description = fields.get("description").cloned();
114
115        let status = fields
116            .get("status")
117            .and_then(|v| v.get("name"))
118            .and_then(|v| v.as_str())
119            .unwrap_or("Unknown")
120            .to_string();
121
122        let assignee = fields
123            .get("assignee")
124            .and_then(|v| v.get("emailAddress").or_else(|| v.get("displayName")))
125            .and_then(|v| v.as_str())
126            .map(|s| s.to_string());
127
128        let reporter = fields
129            .get("reporter")
130            .and_then(|v| v.get("emailAddress").or_else(|| v.get("displayName")))
131            .and_then(|v| v.as_str())
132            .map(|s| s.to_string());
133
134        let priority = fields
135            .get("priority")
136            .and_then(|v| v.get("name"))
137            .and_then(|v| v.as_str())
138            .map(|s| s.to_string());
139
140        let issue_type = fields
141            .get("issuetype")
142            .and_then(|v| v.get("name"))
143            .and_then(|v| v.as_str())
144            .unwrap_or("Unknown")
145            .to_string();
146
147        let project_key = fields
148            .get("project")
149            .and_then(|v| v.get("key"))
150            .and_then(|v| v.as_str())
151            .unwrap_or("")
152            .to_string();
153
154        let created = fields
155            .get("created")
156            .and_then(|v| v.as_str())
157            .unwrap_or("")
158            .to_string();
159
160        let updated = fields
161            .get("updated")
162            .and_then(|v| v.as_str())
163            .unwrap_or("")
164            .to_string();
165
166        let attachments = fields
167            .get("attachment")
168            .and_then(|v| v.as_array())
169            .map(|arr| arr.iter().filter_map(Attachment::from_value).collect())
170            .unwrap_or_default();
171
172        let links = fields
173            .get("issuelinks")
174            .and_then(|v| v.as_array())
175            .map(|arr| {
176                arr.iter()
177                    .filter_map(super::link::IssueLink::from_value)
178                    .collect()
179            })
180            .unwrap_or_default();
181
182        Issue {
183            id: self.id,
184            key: self.key,
185            summary,
186            description,
187            status,
188            assignee,
189            reporter,
190            priority,
191            issue_type,
192            project_key,
193            created,
194            updated,
195            attachments,
196            links,
197            fields: self.fields,
198        }
199    }
200}
201
202/// Raw search response from Jira API
203#[derive(Debug, Deserialize)]
204pub(crate) struct RawSearchResponse {
205    pub issues: Vec<RawIssue>,
206    #[serde(rename = "nextPageToken")]
207    pub next_page_token: Option<String>,
208    pub total: Option<u64>,
209}