1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use strum::{AsRefStr, EnumString};
5use time::OffsetDateTime;
6use url::Url;
7
8use crate::Hateoas;
9
10#[cfg(feature = "serde")]
11use super::utils;
12
13#[cfg_attr(
14 feature = "serde",
15 derive(Serialize, Deserialize),
16 serde(rename_all = "SCREAMING_SNAKE_CASE")
17)]
18#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, AsRefStr, EnumString)]
19#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
20#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
21pub enum TaskStatusType {
22 Received,
23 Pending,
24 Scheduled,
25 Rejected,
26 Denied,
27 MovedVis,
28 Bumped,
29 Moved,
30 Cancelled,
31 SystemError,
32 QueuedPass,
33 Configured,
34 Downlinking,
35 Recording,
36 CompletedPass,
37 DataCloud,
38 Processing,
39 ReadyCustom,
40 ProcessedCustom,
41 ErrorCustom,
42 Completed,
43 CompletedError,
44 PushedToCustomer,
45 ErrorPushToCustomer,
46 Invoiced,
47 Paid,
48}
49
50#[cfg_attr(
51 feature = "serde",
52 derive(Serialize, Deserialize),
53 serde(rename_all = "camelCase")
54)]
55#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
56#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
57pub struct TaskStatus {
58 #[cfg_attr(feature = "serde", serde(with = "crate::utils::timestamp"))]
59 pub created: OffsetDateTime,
60 pub status: TaskStatusType,
61 pub reason: String,
62}
63
64#[cfg_attr(
65 feature = "serde",
66 derive(Serialize, Deserialize),
67 serde(rename_all = "SCREAMING_SNAKE_CASE")
68)]
69#[derive(
70 Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, AsRefStr, EnumString,
71)]
72#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
73#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
74pub enum Polarization {
75 #[default]
76 Right,
77 Left,
78 Vertical,
79 Horizontal,
80}
81
82#[cfg_attr(
83 feature = "serde",
84 derive(Serialize, Deserialize),
85 serde(rename_all = "SCREAMING_SNAKE_CASE")
86)]
87#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, AsRefStr, EnumString)]
88#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
89#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
90pub enum TaskType {
91 Before,
92 After,
93 Test,
94 Around,
95 Exact,
96}
97
98#[cfg_attr(
99 feature = "serde",
100 derive(Serialize, Deserialize),
101 serde(rename_all = "camelCase")
102)]
103#[derive(Debug, Clone, PartialEq, Eq)]
104#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
105pub struct Task {
106 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
107 pub created: OffsetDateTime,
108 #[cfg_attr(
109 feature = "serde",
110 serde(default, with = "time::serde::iso8601::option")
111 )]
112 pub modified: Option<OffsetDateTime>,
113 pub found_visibility: bool,
114 #[cfg_attr(feature = "serde", serde(default))]
116 pub internal_meta_data: Option<HashMap<String, String>>,
117 #[cfg_attr(feature = "serde", serde(default))]
119 pub score: Option<u8>,
120 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
121 pub start: OffsetDateTime,
122 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
123 pub end: OffsetDateTime,
124 #[cfg_attr(
125 feature = "serde",
126 serde(default, with = "time::serde::iso8601::option")
127 )]
128 pub visibility_start: Option<OffsetDateTime>,
129 #[cfg_attr(
130 feature = "serde",
131 serde(default, with = "time::serde::iso8601::option")
132 )]
133 pub visibility_end: Option<OffsetDateTime>,
134 pub billable: bool,
135 pub duration_in_seconds: u32,
136 pub task_within_config_window: bool,
137 pub duration: String,
138 pub file_results: Vec<String>,
139 #[cfg_attr(feature = "serde", serde(default))]
140 pub meta_data: Option<HashMap<String, String>>,
141 #[cfg_attr(
142 feature = "serde",
143 serde(rename = "_links", with = "utils::links::serde", default)
144 )]
145 pub links: HashMap<String, Url>,
146}
147
148impl Hateoas for Task {
149 fn get_links(&self) -> &HashMap<String, url::Url> {
150 &self.links
151 }
152
153 fn get_links_mut(&mut self) -> &mut HashMap<String, url::Url> {
154 &mut self.links
155 }
156}
157
158#[cfg_attr(
159 feature = "serde",
160 derive(Serialize, Deserialize),
161 serde(rename_all = "camelCase")
162)]
163#[derive(Debug, Clone, PartialEq, Eq)]
164#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
165pub struct TaskStatusEvent {
166 pub task_request_id: i32,
167 pub task_request_uri: String,
168 pub status_changes: Vec<TaskStatus>,
169}
170
171impl TaskStatusEvent {
172 pub fn latest_status(&self) -> Option<&TaskStatus> {
173 self.status_changes
174 .iter()
175 .max_by_key(|status| status.created)
176 }
177}
178
179#[cfg_attr(
180 feature = "serde",
181 derive(Serialize, Deserialize),
182 serde(rename_all = "camelCase")
183)]
184#[derive(Debug, Clone, PartialEq, Eq)]
185#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
186pub struct TaskRequest {
187 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
188 pub created: OffsetDateTime,
189 #[cfg_attr(
190 feature = "serde",
191 serde(default, with = "time::serde::iso8601::option")
192 )]
193 pub modified: Option<OffsetDateTime>,
194 #[cfg_attr(feature = "serde", serde(default))]
196 pub internal_meta_data: Option<HashMap<String, String>>,
197 #[cfg_attr(feature = "serde", serde(rename = "type"))]
198 pub task_type: TaskType,
199 #[cfg_attr(feature = "serde", serde(default))]
200 pub hours_of_flex: u32,
201 pub duration: u32,
202 pub minimum_duration: u32,
203 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
204 pub target_date: OffsetDateTime,
205 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
206 pub earliest_start: OffsetDateTime,
207 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
208 pub latest_start: OffsetDateTime,
209 pub transmitting: bool,
210 #[cfg_attr(feature = "serde", serde(default))]
211 pub test_file: Option<String>,
212 pub status_changes: Vec<TaskStatus>,
213 pub task_active: bool,
214 pub task_request_scheduled: bool,
215 pub task_request_cancelled: bool,
216 pub flex: bool,
217 pub latest_status_change: TaskStatus,
218 #[cfg_attr(feature = "serde", serde(default))]
219 pub meta_data: Option<HashMap<String, String>>,
220 #[cfg_attr(
221 feature = "serde",
222 serde(rename = "_links", with = "utils::links::serde", default)
223 )]
224 pub links: HashMap<String, Url>,
225}
226
227impl Hateoas for TaskRequest {
228 fn get_links(&self) -> &HashMap<String, url::Url> {
229 &self.links
230 }
231
232 fn get_links_mut(&mut self) -> &mut HashMap<String, url::Url> {
233 &mut self.links
234 }
235}
236
237#[cfg(all(test, feature = "serde"))]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn task_status_event() {
243 let json = r#"
244{
245 "taskRequestId": 189348,
246 "taskRequestUri": "https://test-api.atlasground.com/api/requests/189348",
247 "statusChanges": [
248 {
249 "created": "2025-08-01T19:55:07.061Z",
250 "status": "RECEIVED",
251 "reason": "Saved to Database and awaiting scheduling"
252 },
253 {
254 "created": "2025-08-01T19:55:07.674Z",
255 "status": "SCHEDULED",
256 "reason": "Test Task Scheduled"
257 },
258 {
259 "created": "2025-08-01T19:55:08.986Z",
260 "status": "QUEUED_PASS",
261 "reason": "Pass has been queued to execute"
262 }
263 ]
264}"#;
265 let event: TaskStatusEvent = serde_json::from_str(json).unwrap();
266 assert_eq!(
267 event.latest_status().unwrap().status,
268 TaskStatusType::QueuedPass
269 );
270 }
271
272 #[test]
273 fn task_status_event_float_timestamp() {
274 use time::macros::datetime;
275
276 let json = r#"
277{
278 "taskRequestId": 190029,
279 "taskRequestUri": "https://test-api.atlasground.com/api/requests/190029",
280 "statusChanges": [
281 {
282 "created": 1754409946.362000000,
283 "status": "RECEIVED",
284 "reason": "Saved to Database and awaiting scheduling"
285 }
286 ]
287}"#;
288 let event: TaskStatusEvent = serde_json::from_str(json).unwrap();
289 assert_eq!(
290 datetime!(2025 - 08 - 05 16:05:46.361_999_989).assume_utc(),
291 event.status_changes[0].created
292 );
293 }
294}