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!("CustomFieldEssentialsWithValue multiple was set to false but value contained more than one value: {values:?}")));
186 }
187 }
188 } else {
189 let s: Option<String> = None;
190 state.serialize_field("value", &s)?;
191 }
192 state.end()
193 }
194}
195
196impl<'de> serde::Deserialize<'de> for CustomFieldEssentialsWithValue {
197 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
198 where
199 D: serde::Deserializer<'de>,
200 {
201 #[derive(serde::Deserialize)]
203 #[serde(field_identifier, rename_all = "lowercase")]
204 enum Field {
205 Id,
207 Name,
209 Multiple,
211 Value,
213 }
214
215 struct CustomFieldVisitor;
217
218 impl<'de> serde::de::Visitor<'de> for CustomFieldVisitor {
219 type Value = CustomFieldEssentialsWithValue;
220
221 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
222 formatter.write_str("struct CustomFieldEssentialsWithValue")
223 }
224
225 fn visit_map<V>(self, mut map: V) -> Result<CustomFieldEssentialsWithValue, V::Error>
226 where
227 V: serde::de::MapAccess<'de>,
228 {
229 let mut id = None;
230 let mut name = None;
231 let mut multiple = None;
232 let mut string_value: Option<String> = None;
233 let mut vec_string_value: Option<Vec<String>> = None;
234 while let Some(key) = map.next_key()? {
235 match key {
236 Field::Id => {
237 if id.is_some() {
238 return Err(serde::de::Error::duplicate_field("id"));
239 }
240 id = Some(map.next_value()?);
241 }
242 Field::Name => {
243 if name.is_some() {
244 return Err(serde::de::Error::duplicate_field("name"));
245 }
246 name = Some(map.next_value()?);
247 }
248 Field::Multiple => {
249 if multiple.is_some() {
250 return Err(serde::de::Error::duplicate_field("multiple"));
251 }
252 multiple = Some(map.next_value()?);
253 }
254 Field::Value => {
255 if string_value.is_some() {
256 return Err(serde::de::Error::duplicate_field("value"));
257 }
258 if vec_string_value.is_some() {
259 return Err(serde::de::Error::duplicate_field("value"));
260 }
261 if let Some(true) = multiple {
262 vec_string_value = Some(map.next_value()?);
263 } else {
264 string_value = map.next_value()?;
265 }
266 }
267 }
268 }
269 let id = id.ok_or_else(|| serde::de::Error::missing_field("id"))?;
270 let name = name.ok_or_else(|| serde::de::Error::missing_field("name"))?;
271 match (multiple, string_value, vec_string_value) {
272 (None, None, None) => Ok(CustomFieldEssentialsWithValue {
273 id,
274 name,
275 multiple: None,
276 value: None,
277 }),
278 (None, Some(s), None) => Ok(CustomFieldEssentialsWithValue {
279 id,
280 name,
281 multiple: None,
282 value: Some(vec![s]),
283 }),
284 (Some(true), None, Some(v)) => Ok(CustomFieldEssentialsWithValue {
285 id,
286 name,
287 multiple: Some(true),
288 value: Some(v),
289 }),
290 _ => Err(serde::de::Error::custom(
291 "invalid combination of multiple and value",
292 )),
293 }
294 }
295 }
296
297 const FIELDS: &[&str] = &["id", "name", "multiple", "value"];
299 deserializer.deserialize_struct(
300 "CustomFieldEssentialsWithValue",
301 FIELDS,
302 CustomFieldVisitor,
303 )
304 }
305}
306
307#[derive(Debug, Clone, Builder)]
309#[builder(setter(strip_option))]
310pub struct ListCustomFields {}
311
312impl ReturnsJsonResponse for ListCustomFields {}
313impl NoPagination for ListCustomFields {}
314
315impl ListCustomFields {
316 #[must_use]
318 pub fn builder() -> ListCustomFieldsBuilder {
319 ListCustomFieldsBuilder::default()
320 }
321}
322
323impl Endpoint for ListCustomFields {
324 fn method(&self) -> Method {
325 Method::GET
326 }
327
328 fn endpoint(&self) -> Cow<'static, str> {
329 "custom_fields.json".into()
330 }
331}
332
333#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
335pub struct CustomFieldsWrapper<T> {
336 pub custom_fields: Vec<T>,
338}
339
340#[cfg(test)]
341mod test {
342 use super::*;
343 use pretty_assertions::assert_eq;
344 use std::error::Error;
345 use tracing_test::traced_test;
346
347 #[traced_test]
348 #[test]
349 fn test_list_custom_fields_no_pagination() -> Result<(), Box<dyn Error>> {
350 dotenvy::dotenv()?;
351 let redmine = crate::api::Redmine::from_env(
352 reqwest::blocking::Client::builder()
353 .use_rustls_tls()
354 .build()?,
355 )?;
356 let endpoint = ListCustomFields::builder().build()?;
357 redmine.json_response_body::<_, CustomFieldsWrapper<CustomField>>(&endpoint)?;
358 Ok(())
359 }
360
361 #[traced_test]
366 #[test]
367 fn test_completeness_custom_fields_type() -> Result<(), Box<dyn Error>> {
368 dotenvy::dotenv()?;
369 let redmine = crate::api::Redmine::from_env(
370 reqwest::blocking::Client::builder()
371 .use_rustls_tls()
372 .build()?,
373 )?;
374 let endpoint = ListCustomFields::builder().build()?;
375 let CustomFieldsWrapper {
376 custom_fields: values,
377 } = redmine.json_response_body::<_, CustomFieldsWrapper<serde_json::Value>>(&endpoint)?;
378 for value in values {
379 let o: CustomField = serde_json::from_value(value.clone())?;
380 let reserialized = serde_json::to_value(o)?;
381 assert_eq!(value, reserialized);
382 }
383 Ok(())
384 }
385}