gitlab_time_report/fetch_api/
deserializer.rs1use crate::model::{
4 Issue, MergeRequest, TrackableItem, TrackableItemFields, TrackableItemKind, UserNodes,
5};
6use serde::{Deserialize, Deserializer};
7
8impl<'de> Deserialize<'de> for TrackableItem {
16 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
17 #[derive(Deserialize)]
20 #[serde(rename_all = "camelCase")]
21 struct TrackableItemDeserialize {
22 issue: Option<IssueDeserialize>,
23 merge_request: Option<MergeRequestDeserialize>,
24 }
25
26 #[derive(Deserialize)]
27 struct IssueDeserialize {
28 #[serde(flatten)]
29 common: TrackableItemFields,
30 }
31
32 #[derive(Deserialize)]
33 struct MergeRequestDeserialize {
34 #[serde(flatten)]
35 common: TrackableItemFields,
36 reviewers: UserNodes,
37 }
38
39 let trackable_item_from_api = TrackableItemDeserialize::deserialize(deserializer)?;
41
42 let real_trackable_item = match (
44 trackable_item_from_api.issue,
45 trackable_item_from_api.merge_request,
46 ) {
47 (Some(issue), None) => TrackableItem {
48 common: issue.common,
49 kind: TrackableItemKind::Issue(Issue {}),
50 },
51 (None, Some(mr)) => TrackableItem {
52 common: mr.common,
53 kind: TrackableItemKind::MergeRequest(MergeRequest {
54 reviewers: mr.reviewers,
55 }),
56 },
57 (Some(_), Some(_)) => {
58 return Err(serde::de::Error::custom(
59 "Both issue and mergeRequest are present, expected only one",
60 ));
61 }
62 (None, None) => {
63 return Err(serde::de::Error::custom(
64 "Neither issue nor mergeRequest is present, expected one",
65 ));
66 }
67 };
68
69 Ok(real_trackable_item)
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::model::User;
77
78 #[test]
79 fn deserialize_issue() {
80 let json_str = r#"{"issue":{"iid":"1","title":"Issue Name","timeEstimate":3600,"totalTimeSpent":10800,"assignees":{"nodes":[]},"milestone":null,"labels":{"nodes":[{"title":"Project Management"}]}},"mergeRequest":null}"#;
81 let deserialized: TrackableItem = serde_json::from_str(json_str).unwrap();
82 assert_eq!(deserialized.common.title, "Issue Name");
83 assert_eq!(
84 deserialized.common.time_estimate,
85 chrono::Duration::seconds(3600)
86 );
87 assert_eq!(
88 deserialized.common.total_time_spent,
89 chrono::Duration::seconds(10800)
90 );
91 assert!(matches!(deserialized.kind, TrackableItemKind::Issue(_)));
92 }
93
94 #[test]
95 fn deserialize_mr() {
96 let json_str = r#"{"issue":null,"mergeRequest":{"iid":"1","title":"Update README.md","timeEstimate":1800,"totalTimeSpent":1800,"assignees":{"nodes":[]},"reviewers":{"nodes":[{"name":"User1","username":"user.1"}]},"milestone":null,"labels":{"nodes":[]}}}"#;
97 let reviewers = UserNodes {
98 users: vec![User {
99 name: "User1".to_string(),
100 username: "user.1".to_string(),
101 }],
102 };
103
104 let deserialized: TrackableItem = serde_json::from_str(json_str).unwrap();
105 assert_eq!(deserialized.common.title, "Update README.md");
106 assert_eq!(
107 deserialized.common.time_estimate,
108 chrono::Duration::seconds(1800)
109 );
110 assert_eq!(
111 deserialized.common.total_time_spent,
112 chrono::Duration::seconds(1800)
113 );
114 assert!(matches!(
115 deserialized.kind,
116 TrackableItemKind::MergeRequest(_)
117 ));
118
119 if let TrackableItemKind::MergeRequest(deserialized_mr) = deserialized.kind {
120 assert_eq!(deserialized_mr.reviewers, reviewers);
121 }
122 }
123
124 #[test]
125 fn deserialize_mr_and_issue_present() {
126 let json_str = r#"{"issue":{"iid":"1","title":"Issue Name","timeEstimate":3600,"totalTimeSpent":10800,"assignees":{"nodes":[]},"milestone":null,"labels":{"nodes":[]}},"mergeRequest":{"iid":"1","title":"Update README.md","timeEstimate":1800,"totalTimeSpent":1800,"assignees":{"nodes":[]},"reviewers":{"nodes":[]},"milestone":null,"labels":{"nodes":[]}}}"#;
127 let deserialized = serde_json::from_str::<TrackableItem>(json_str);
128
129 assert!(deserialized.is_err());
130 let error = deserialized.unwrap_err();
131 assert!(error.is_data());
132 assert_eq!(
133 error.to_string(),
134 "Both issue and mergeRequest are present, expected only one"
135 );
136 }
137
138 #[test]
139 fn deserialize_mr_and_issue_null() {
140 let json_str = r#"{"issue":null,"mergeRequest":null}"#;
141 let deserialized = serde_json::from_str::<TrackableItem>(json_str);
142
143 assert!(deserialized.is_err());
144 let error = deserialized.unwrap_err();
145 assert!(error.is_data());
146 assert_eq!(
147 error.to_string(),
148 "Neither issue nor mergeRequest is present, expected one"
149 );
150 }
151}