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, NoPagination, 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 editable: bool,
94 pub customized_type: CustomizedType,
96 pub field_format: FieldFormat,
98 pub regexp: Option<String>,
100 pub min_length: Option<usize>,
102 pub max_length: Option<usize>,
104 pub is_required: Option<bool>,
106 pub is_filter: Option<bool>,
108 pub searchable: bool,
110 pub multiple: bool,
112 pub default_value: Option<String>,
114 pub visible: bool,
116 pub roles: Vec<RoleEssentials>,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub possible_values: Option<Vec<PossibleValue>>,
121 pub trackers: Vec<TrackerEssentials>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub projects: Option<Vec<ProjectEssentials>>,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct CustomFieldEssentialsWithValue {
132 pub id: u64,
134 pub name: String,
136 pub multiple: Option<bool>,
138 pub value: Option<Vec<String>>,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
144pub struct CustomFieldName {
145 pub id: u64,
147 pub name: String,
149}
150
151impl serde::Serialize for CustomFieldEssentialsWithValue {
152 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
153 where
154 S: serde::Serializer,
155 {
156 use serde::ser::SerializeStruct;
157 let mut len = 2;
158 if self.multiple.is_some() {
159 len += 1;
160 };
161 if self.value.is_some() {
162 len += 1;
163 }
164 let mut state = serializer.serialize_struct("CustomFieldEssentialsWithValue", len)?;
165 state.serialize_field("id", &self.id)?;
166 state.serialize_field("name", &self.name)?;
167 if let Some(ref multiple) = self.multiple {
168 state.serialize_field("multiple", &multiple)?;
169 if let Some(ref value) = self.value {
170 state.serialize_field("value", &value)?;
171 } else {
172 let s: Option<Vec<String>> = None;
173 state.serialize_field("value", &s)?;
174 }
175 } else if let Some(ref value) = self.value {
176 match value.as_slice() {
177 [] => {
178 let s: Option<String> = None;
179 state.serialize_field("value", &s)?;
180 }
181 [s] => {
182 state.serialize_field("value", &s)?;
183 }
184 values => {
185 return Err(serde::ser::Error::custom(format!(
186 "CustomFieldEssentialsWithValue multiple was set to false but value contained more than one value: {values:?}"
187 )));
188 }
189 }
190 } else {
191 let s: Option<String> = None;
192 state.serialize_field("value", &s)?;
193 }
194 state.end()
195 }
196}
197
198impl<'de> serde::Deserialize<'de> for CustomFieldEssentialsWithValue {
199 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
200 where
201 D: serde::Deserializer<'de>,
202 {
203 #[derive(serde::Deserialize)]
205 #[serde(field_identifier, rename_all = "lowercase")]
206 enum Field {
207 Id,
209 Name,
211 Multiple,
213 Value,
215 }
216
217 struct CustomFieldVisitor;
219
220 impl<'de> serde::de::Visitor<'de> for CustomFieldVisitor {
221 type Value = CustomFieldEssentialsWithValue;
222
223 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
224 formatter.write_str("struct CustomFieldEssentialsWithValue")
225 }
226
227 fn visit_map<V>(self, mut map: V) -> Result<CustomFieldEssentialsWithValue, V::Error>
228 where
229 V: serde::de::MapAccess<'de>,
230 {
231 let mut id = None;
232 let mut name = None;
233 let mut multiple = None;
234 let mut string_value: Option<String> = None;
235 let mut vec_string_value: Option<Vec<String>> = None;
236 while let Some(key) = map.next_key()? {
237 match key {
238 Field::Id => {
239 if id.is_some() {
240 return Err(serde::de::Error::duplicate_field("id"));
241 }
242 id = Some(map.next_value()?);
243 }
244 Field::Name => {
245 if name.is_some() {
246 return Err(serde::de::Error::duplicate_field("name"));
247 }
248 name = Some(map.next_value()?);
249 }
250 Field::Multiple => {
251 if multiple.is_some() {
252 return Err(serde::de::Error::duplicate_field("multiple"));
253 }
254 multiple = Some(map.next_value()?);
255 }
256 Field::Value => {
257 if string_value.is_some() {
258 return Err(serde::de::Error::duplicate_field("value"));
259 }
260 if vec_string_value.is_some() {
261 return Err(serde::de::Error::duplicate_field("value"));
262 }
263 if let Some(true) = multiple {
264 vec_string_value = Some(map.next_value()?);
265 } else {
266 string_value = map.next_value()?;
267 }
268 }
269 }
270 }
271 let id = id.ok_or_else(|| serde::de::Error::missing_field("id"))?;
272 let name = name.ok_or_else(|| serde::de::Error::missing_field("name"))?;
273 match (multiple, string_value, vec_string_value) {
274 (None, None, None) => Ok(CustomFieldEssentialsWithValue {
275 id,
276 name,
277 multiple: None,
278 value: None,
279 }),
280 (None, Some(s), None) => Ok(CustomFieldEssentialsWithValue {
281 id,
282 name,
283 multiple: None,
284 value: Some(vec![s]),
285 }),
286 (Some(true), None, Some(v)) => Ok(CustomFieldEssentialsWithValue {
287 id,
288 name,
289 multiple: Some(true),
290 value: Some(v),
291 }),
292 _ => Err(serde::de::Error::custom(
293 "invalid combination of multiple and value",
294 )),
295 }
296 }
297 }
298
299 const FIELDS: &[&str] = &["id", "name", "multiple", "value"];
301 deserializer.deserialize_struct(
302 "CustomFieldEssentialsWithValue",
303 FIELDS,
304 CustomFieldVisitor,
305 )
306 }
307}
308
309#[derive(Debug, Clone, Builder)]
311#[builder(setter(strip_option))]
312pub struct ListCustomFields {}
313
314impl ReturnsJsonResponse for ListCustomFields {}
315impl NoPagination for ListCustomFields {}
316
317impl ListCustomFields {
318 #[must_use]
320 pub fn builder() -> ListCustomFieldsBuilder {
321 ListCustomFieldsBuilder::default()
322 }
323}
324
325impl Endpoint for ListCustomFields {
326 fn method(&self) -> Method {
327 Method::GET
328 }
329
330 fn endpoint(&self) -> Cow<'static, str> {
331 "custom_fields.json".into()
332 }
333}
334
335#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
337pub struct CustomFieldsWrapper<T> {
338 pub custom_fields: Vec<T>,
340}
341
342#[cfg(test)]
343mod test {
344 use super::*;
345 use pretty_assertions::assert_eq;
346 use std::error::Error;
347 use tracing_test::traced_test;
348
349 #[traced_test]
350 #[test]
351 fn test_list_custom_fields_no_pagination() -> Result<(), Box<dyn Error>> {
352 dotenvy::dotenv()?;
353 let redmine = crate::api::Redmine::from_env(
354 reqwest::blocking::Client::builder()
355 .use_rustls_tls()
356 .build()?,
357 )?;
358 let endpoint = ListCustomFields::builder().build()?;
359 redmine.json_response_body::<_, CustomFieldsWrapper<CustomField>>(&endpoint)?;
360 Ok(())
361 }
362
363 #[traced_test]
368 #[test]
369 fn test_completeness_custom_fields_type() -> Result<(), Box<dyn Error>> {
370 dotenvy::dotenv()?;
371 let redmine = crate::api::Redmine::from_env(
372 reqwest::blocking::Client::builder()
373 .use_rustls_tls()
374 .build()?,
375 )?;
376 let endpoint = ListCustomFields::builder().build()?;
377 let CustomFieldsWrapper {
378 custom_fields: values,
379 } = redmine.json_response_body::<_, CustomFieldsWrapper<serde_json::Value>>(&endpoint)?;
380 for value in values {
381 let o: CustomField = serde_json::from_value(value.clone())?;
382 let reserialized = serde_json::to_value(o)?;
383 assert_eq!(value, reserialized);
384 }
385 Ok(())
386 }
387}