1use std::borrow::Cow;
9
10use derive_builder::Builder;
11use reqwest::Method;
12use serde::Serialize;
13
14use crate::api::users::UserEssentials;
15use crate::api::{Endpoint, NoPagination, QueryParams, ReturnsJsonResponse};
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
21pub struct File {
22 pub id: u64,
24 pub filename: String,
26 pub filesize: u64,
28 pub content_type: String,
30 pub description: String,
32 pub token: String,
34 pub author: UserEssentials,
36 #[serde(
38 serialize_with = "crate::api::serialize_rfc3339",
39 deserialize_with = "crate::api::deserialize_rfc3339"
40 )]
41 pub created_on: time::OffsetDateTime,
42 pub digest: String,
44 pub downloads: u64,
46}
47
48#[derive(Debug, Clone, Builder)]
50#[builder(setter(strip_option))]
51pub struct ListProjectFiles<'a> {
52 #[builder(setter(into))]
54 project_id_or_name: Cow<'a, str>,
55}
56
57impl ReturnsJsonResponse for ListProjectFiles<'_> {}
58impl NoPagination for ListProjectFiles<'_> {}
59
60impl<'a> ListProjectFiles<'a> {
61 #[must_use]
63 pub fn builder() -> ListProjectFilesBuilder<'a> {
64 ListProjectFilesBuilder::default()
65 }
66}
67
68impl Endpoint for ListProjectFiles<'_> {
69 fn method(&self) -> Method {
70 Method::GET
71 }
72
73 fn endpoint(&self) -> Cow<'static, str> {
74 format!("projects/{}/files.json", self.project_id_or_name).into()
75 }
76
77 fn parameters(&self) -> QueryParams<'_> {
78 QueryParams::default()
79 }
80}
81
82#[derive(Debug, Clone, Builder, Serialize)]
84#[builder(setter(strip_option))]
85pub struct CreateFile<'a> {
86 #[builder(setter(into))]
88 #[serde(skip_serializing)]
89 project_id_or_name: Cow<'a, str>,
90 #[builder(setter(into))]
92 token: Cow<'a, str>,
93 #[builder(default)]
95 version_id: Option<u64>,
96 #[builder(setter(into), default)]
98 filename: Option<Cow<'a, str>>,
99 #[builder(setter(into), default)]
101 description: Option<Cow<'a, str>>,
102}
103
104impl<'a> CreateFile<'a> {
105 #[must_use]
107 pub fn builder() -> CreateFileBuilder<'a> {
108 CreateFileBuilder::default()
109 }
110}
111
112impl Endpoint for CreateFile<'_> {
113 fn method(&self) -> Method {
114 Method::POST
115 }
116
117 fn endpoint(&self) -> Cow<'static, str> {
118 format!("projects/{}/files.json", self.project_id_or_name).into()
119 }
120
121 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, crate::Error> {
122 Ok(Some((
123 "application/json",
124 serde_json::to_vec(&FileWrapper::<CreateFile> {
125 file: (*self).to_owned(),
126 })?,
127 )))
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
133pub struct FileWrapper<T> {
134 pub file: T,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
140pub struct FilesWrapper<T> {
141 pub files: Vec<T>,
143}
144
145#[cfg(test)]
146mod test {
147 use super::*;
148 use crate::api::uploads::UploadFile;
149 use pretty_assertions::assert_eq;
150 use std::error::Error;
151 use tempfile;
152 use tracing_test::traced_test;
153
154 #[function_name::named]
155 #[traced_test]
156 #[test]
157 fn test_list_project_files_no_pagination() -> Result<(), Box<dyn Error>> {
158 let name = format!("unittest_{}", function_name!());
159 crate::api::test_helpers::with_project(&name, |redmine, _id, name| {
160 let endpoint = ListProjectFiles::builder()
161 .project_id_or_name(name)
162 .build()?;
163 redmine.json_response_body::<_, FilesWrapper<File>>(&endpoint)?;
164 Ok(())
165 })?;
166 Ok(())
167 }
168
169 #[function_name::named]
170 #[traced_test]
171 #[test]
172 fn test_create_file() -> Result<(), Box<dyn Error>> {
173 let name = format!("unittest_{}", function_name!());
174 crate::api::test_helpers::with_project(&name, |redmine, _id, name| {
175 let mut temp_file = tempfile::NamedTempFile::new()?;
176 use std::io::Write;
177 write!(temp_file, "test file content")?;
178 let upload_endpoint = UploadFile::builder()
179 .file(temp_file.path().to_path_buf())
180 .content_type("text/plain")
181 .build()?;
182 let upload: crate::api::uploads::UploadWrapper<crate::api::uploads::FileUploadToken> =
183 redmine.json_response_body(&upload_endpoint)?;
184 let endpoint = CreateFile::builder()
185 .project_id_or_name(name)
186 .token(upload.upload.token)
187 .build()?;
188 redmine.ignore_response_body(&endpoint)?;
189 Ok(())
190 })?;
191 Ok(())
192 }
193
194 #[function_name::named]
199 #[traced_test]
200 #[test]
201 fn test_completeness_file_type() -> Result<(), Box<dyn Error>> {
202 let name = format!("unittest_{}", function_name!());
203 crate::api::test_helpers::with_project(&name, |redmine, _id, name| {
204 let endpoint = ListProjectFiles::builder()
205 .project_id_or_name(name)
206 .build()?;
207 let raw_values =
208 redmine.json_response_body::<_, FilesWrapper<serde_json::Value>>(&endpoint)?;
209 for value in raw_values.files {
210 let o: File = serde_json::from_value(value.clone())?;
211 let reserialized = serde_json::to_value(o)?;
212 assert_eq!(value, reserialized);
213 }
214 Ok(())
215 })?;
216 Ok(())
217 }
218}