1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! Interfaces for accessing and managing attachments
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
// Ours
use crate::{Jira, Result};
#[cfg(feature = "async")]
use crate::r#async::Jira as AsyncJira;
/// Same as `User`, but without `email_address`
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserResponse {
pub active: bool,
#[serde(rename = "avatarUrls")]
pub avatar_urls: BTreeMap<String, String>,
#[serde(rename = "displayName")]
pub display_name: String,
pub key: Option<String>,
pub name: String,
#[serde(rename = "self")]
pub self_link: String,
#[serde(rename = "timeZone")]
pub timezone: Option<String>,
}
/// Same as `Attachement`, but without `id` and with `UserResponse`
#[derive(Serialize, Deserialize, Debug)]
pub struct AttachmentResponse {
#[serde(rename = "self")]
pub self_link: String,
pub filename: String,
pub author: UserResponse,
pub created: String,
pub size: u64,
#[serde(rename = "mimeType")]
pub mime_type: String,
pub content: String,
pub thumbnail: Option<String>,
}
#[derive(Debug)]
pub struct Attachments {
jira: Jira,
}
impl Attachments {
pub fn new(jira: &Jira) -> Attachments {
Attachments { jira: jira.clone() }
}
/// Get the meta-data of a single attachment
///
/// See this [jira docs](https://docs.atlassian.com/software/jira/docs/api/REST/8.13.8/#api/2/attachment-getAttachment)
/// for more information
pub fn get<I>(&self, id: I) -> Result<AttachmentResponse>
where
I: Into<String>,
{
self.jira.get("api", &format!("/attachment/{}", id.into()))
}
/// Delete a single attachment
///
/// See this [jira docs](https://docs.atlassian.com/software/jira/docs/api/REST/8.13.8/#api/2/attachment-removeAttachment)
/// for more information
pub fn delete<I>(&self, id: I) -> Result<()>
where
I: Into<String>,
{
self.jira
.delete::<crate::EmptyResponse>("api", &format!("/attachment/{}", id.into()))?;
Ok(())
}
/// Download attachment content as raw bytes
///
/// This method retrieves the actual file content of an attachment. It automatically
/// handles authentication using the same credentials as other API calls.
///
/// # Arguments
///
/// * `id` - The attachment ID
///
/// # Returns
///
/// `Result<Vec<u8>>` - The raw attachment content as bytes
///
/// # Examples
///
/// ```no_run
/// # use gouqi::{Credentials, Jira};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let jira = Jira::new("https://jira.example.com", Credentials::Anonymous)?;
/// let content_bytes = jira.attachments().download("12345")?;
///
/// // Save to file
/// std::fs::write("attachment.pdf", &content_bytes)?;
/// # Ok(())
/// # }
/// ```
///
/// See this [jira docs](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/#api-rest-api-3-attachment-content-id-get)
/// for more information
pub fn download<I>(&self, id: I) -> Result<Vec<u8>>
where
I: Into<String>,
{
// The attachment content endpoint returns raw bytes, not JSON
self.jira
.get_bytes("api", &format!("/attachment/content/{}", id.into()))
}
}
#[cfg(feature = "async")]
#[derive(Debug)]
pub struct AsyncAttachments {
jira: AsyncJira,
}
#[cfg(feature = "async")]
impl AsyncAttachments {
pub fn new(jira: &AsyncJira) -> AsyncAttachments {
AsyncAttachments { jira: jira.clone() }
}
/// Get the meta-data of a single attachment
///
/// See this [jira docs](https://docs.atlassian.com/software/jira/docs/api/REST/8.13.8/#api/2/attachment-getAttachment)
/// for more information
pub async fn get<I>(&self, id: I) -> Result<AttachmentResponse>
where
I: Into<String>,
{
self.jira
.get("api", &format!("/attachment/{}", id.into()))
.await
}
/// Delete a single attachment
///
/// See this [jira docs](https://docs.atlassian.com/software/jira/docs/api/REST/8.13.8/#api/2/attachment-removeAttachment)
/// for more information
pub async fn delete<I>(&self, id: I) -> Result<()>
where
I: Into<String>,
{
self.jira
.delete::<crate::EmptyResponse>("api", &format!("/attachment/{}", id.into()))
.await?;
Ok(())
}
/// Download attachment content as raw bytes (async)
///
/// This method retrieves the actual file content of an attachment. It automatically
/// handles authentication using the same credentials as other API calls.
///
/// # Arguments
///
/// * `id` - The attachment ID
///
/// # Returns
///
/// `Result<Vec<u8>>` - The raw attachment content as bytes
///
/// # Examples
///
/// ```no_run
/// # #[cfg(feature = "async")]
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// use gouqi::{Credentials, r#async::Jira};
///
/// let jira = Jira::new("https://jira.example.com", Credentials::Anonymous)?;
/// let content_bytes = jira.attachments().download("12345").await?;
///
/// // Save to file
/// std::fs::write("attachment.pdf", &content_bytes)?;
/// # Ok(())
/// # }
/// ```
///
/// See this [jira docs](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/#api-rest-api-3-attachment-content-id-get)
/// for more information
pub async fn download<I>(&self, id: I) -> Result<Vec<u8>>
where
I: Into<String>,
{
// The attachment content endpoint returns raw bytes, not JSON
self.jira
.get_bytes("api", &format!("/attachment/content/{}", id.into()))
.await
}
}