1use derive_builder::Builder;
8use reqwest::Method;
9use std::borrow::Cow;
10
11use crate::api::projects::ProjectEssentials;
12use crate::api::roles::RoleEssentials;
13use crate::api::trackers::TrackerEssentials;
14use crate::api::{Endpoint, ReturnsJsonResponse};
15
16#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum CustomizedType {
21 Issue,
23 TimeEntry,
25 Project,
27 Version,
29 User,
31 Group,
33 Activity,
35 IssuePriority,
37 DocumentCategory,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum FieldFormat {
45 Bool,
47 Date,
49 File,
51 Float,
53 Integer,
55 KeyValueList,
57 Link,
59 List,
61 Text,
63 String,
65 User,
67 Version,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
73#[serde(rename_all = "snake_case")]
74pub struct PossibleValue {
75 pub label: String,
77 pub value: String,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
85pub struct CustomField {
86 pub id: u64,
88 pub name: String,
90 pub description: Option<String>,
92 pub customized_type: CustomizedType,
94 pub field_format: FieldFormat,
96 pub regexp: Option<String>,
98 pub min_length: Option<usize>,
100 pub max_length: Option<usize>,
102 pub is_required: Option<bool>,
104 pub is_filter: Option<bool>,
106 pub searchable: bool,
108 pub multiple: bool,
110 pub default_value: Option<String>,
112 pub visible: bool,
114 pub roles: Vec<RoleEssentials>,
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub possible_values: Option<Vec<PossibleValue>>,
119 pub trackers: Vec<TrackerEssentials>,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub projects: Option<Vec<ProjectEssentials>>,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
129pub struct CustomFieldEssentialsWithValue {
130 pub id: u64,
132 pub name: String,
134 pub multiple: Option<bool>,
136 pub value: Option<Vec<String>>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
142pub struct CustomFieldName {
143 pub id: u64,
145 pub name: String,
147}
148
149impl serde::Serialize for CustomFieldEssentialsWithValue {
150 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
151 where
152 S: serde::Serializer,
153 {
154 use serde::ser::SerializeStruct;
155 let mut len = 2;
156 if self.multiple.is_some() {
157 len += 1;
158 };
159 if self.value.is_some() {
160 len += 1;
161 }
162 let mut state = serializer.serialize_struct("CustomFieldEssentialsWithValue", len)?;
163 state.serialize_field("id", &self.id)?;
164 state.serialize_field("name", &self.name)?;
165 if let Some(ref multiple) = self.multiple {
166 state.serialize_field("multiple", &multiple)?;
167 if let Some(ref value) = self.value {
168 state.serialize_field("value", &value)?;
169 } else {
170 let s: Option<Vec<String>> = None;
171 state.serialize_field("value", &s)?;
172 }
173 } else if let Some(ref value) = self.value {
174 match value.as_slice() {
175 [] => {
176 let s: Option<String> = None;
177 state.serialize_field("value", &s)?;
178 }
179 [s] => {
180 state.serialize_field("value", &s)?;
181 }
182 values => {
183 return Err(serde::ser::Error::custom(format!("CustomFieldEssentialsWithValue multiple was set to false but value contained more than one value: {:?}", values)));
184 }
185 }
186 } else {
187 let s: Option<String> = None;
188 state.serialize_field("value", &s)?;
189 }
190 state.end()
191 }
192}
193
194impl<'de> serde::Deserialize<'de> for CustomFieldEssentialsWithValue {
195 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
196 where
197 D: serde::Deserializer<'de>,
198 {
199 #[derive(serde::Deserialize)]
201 #[serde(field_identifier, rename_all = "lowercase")]
202 enum Field {
203 Id,
205 Name,
207 Multiple,
209 Value,
211 }
212
213 struct CustomFieldVisitor;
215
216 impl<'de> serde::de::Visitor<'de> for CustomFieldVisitor {
217 type Value = CustomFieldEssentialsWithValue;
218
219 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
220 formatter.write_str("struct CustomFieldEssentialsWithValue")
221 }
222
223 fn visit_map<V>(self, mut map: V) -> Result<CustomFieldEssentialsWithValue, V::Error>
224 where
225 V: serde::de::MapAccess<'de>,
226 {
227 let mut id = None;
228 let mut name = None;
229 let mut multiple = None;
230 let mut string_value: Option<String> = None;
231 let mut vec_string_value: Option<Vec<String>> = None;
232 while let Some(key) = map.next_key()? {
233 match key {
234 Field::Id => {
235 if id.is_some() {
236 return Err(serde::de::Error::duplicate_field("id"));
237 }
238 id = Some(map.next_value()?);
239 }
240 Field::Name => {
241 if name.is_some() {
242 return Err(serde::de::Error::duplicate_field("name"));
243 }
244 name = Some(map.next_value()?);
245 }
246 Field::Multiple => {
247 if multiple.is_some() {
248 return Err(serde::de::Error::duplicate_field("multiple"));
249 }
250 multiple = Some(map.next_value()?);
251 }
252 Field::Value => {
253 if string_value.is_some() {
254 return Err(serde::de::Error::duplicate_field("value"));
255 }
256 if vec_string_value.is_some() {
257 return Err(serde::de::Error::duplicate_field("value"));
258 }
259 if let Some(true) = multiple {
260 vec_string_value = Some(map.next_value()?);
261 } else {
262 string_value = map.next_value()?;
263 }
264 }
265 }
266 }
267 let id = id.ok_or_else(|| serde::de::Error::missing_field("id"))?;
268 let name = name.ok_or_else(|| serde::de::Error::missing_field("name"))?;
269 match (multiple, string_value, vec_string_value) {
270 (None, None, None) => Ok(CustomFieldEssentialsWithValue {
271 id,
272 name,
273 multiple: None,
274 value: None,
275 }),
276 (None, Some(s), None) => Ok(CustomFieldEssentialsWithValue {
277 id,
278 name,
279 multiple: None,
280 value: Some(vec![s]),
281 }),
282 (Some(true), None, Some(v)) => Ok(CustomFieldEssentialsWithValue {
283 id,
284 name,
285 multiple: Some(true),
286 value: Some(v),
287 }),
288 _ => Err(serde::de::Error::custom(
289 "invalid combination of multiple and value",
290 )),
291 }
292 }
293 }
294
295 const FIELDS: &[&str] = &["id", "name", "multiple", "value"];
297 deserializer.deserialize_struct(
298 "CustomFieldEssentialsWithValue",
299 FIELDS,
300 CustomFieldVisitor,
301 )
302 }
303}
304
305#[derive(Debug, Clone, Builder)]
307#[builder(setter(strip_option))]
308pub struct ListCustomFields {}
309
310impl ReturnsJsonResponse for ListCustomFields {}
311
312impl ListCustomFields {
313 #[must_use]
315 pub fn builder() -> ListCustomFieldsBuilder {
316 ListCustomFieldsBuilder::default()
317 }
318}
319
320impl Endpoint for ListCustomFields {
321 fn method(&self) -> Method {
322 Method::GET
323 }
324
325 fn endpoint(&self) -> Cow<'static, str> {
326 "custom_fields.json".into()
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
332pub struct CustomFieldsWrapper<T> {
333 pub custom_fields: Vec<T>,
335}
336
337#[cfg(test)]
338mod test {
339 use super::*;
340 use pretty_assertions::assert_eq;
341 use std::error::Error;
342 use tracing_test::traced_test;
343
344 #[traced_test]
345 #[test]
346 fn test_list_custom_fields_no_pagination() -> Result<(), Box<dyn Error>> {
347 dotenvy::dotenv()?;
348 let redmine = crate::api::Redmine::from_env()?;
349 let endpoint = ListCustomFields::builder().build()?;
350 redmine.json_response_body::<_, CustomFieldsWrapper<CustomField>>(&endpoint)?;
351 Ok(())
352 }
353
354 #[traced_test]
359 #[test]
360 fn test_completeness_custom_fields_type() -> Result<(), Box<dyn Error>> {
361 dotenvy::dotenv()?;
362 let redmine = crate::api::Redmine::from_env()?;
363 let endpoint = ListCustomFields::builder().build()?;
364 let CustomFieldsWrapper {
365 custom_fields: values,
366 } = redmine.json_response_body::<_, CustomFieldsWrapper<serde_json::Value>>(&endpoint)?;
367 for value in values {
368 let o: CustomField = serde_json::from_value(value.clone())?;
369 let reserialized = serde_json::to_value(o)?;
370 assert_eq!(value, reserialized);
371 }
372 Ok(())
373 }
374}