1use anyhow::Result;
2use quick_xml::events::Event;
3use quick_xml::Reader;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
7pub struct JobSummary {
8 pub job_id: String,
9 pub url: String,
10}
11
12#[derive(Debug, Clone)]
13pub struct JobStatus {
14 pub job_id: String,
15 pub job_stage: String,
16 pub failed: bool,
17 pub date_submitted: Option<String>,
18 pub self_uri: String,
19 pub results_uri: Option<String>,
20 pub messages: Vec<JobMessage>,
21}
22
23#[derive(Debug, Clone)]
24pub struct JobMessage {
25 pub stage: String,
26 pub text: String,
27 pub timestamp: Option<String>,
28}
29
30#[derive(Debug, Clone)]
31pub struct OutputFile {
32 pub filename: String,
33 pub download_uri: String,
34 pub size: u64,
35}
36
37#[derive(Debug, Clone)]
38pub struct DownloadedFile {
39 pub filename: String,
40 pub path: PathBuf,
41 pub size: u64,
42}
43
44pub fn parse_job_list(xml: &str) -> Result<Vec<JobSummary>> {
45 let mut reader = Reader::from_str(xml);
46 reader.config_mut().trim_text(true);
47
48 let mut jobs = Vec::new();
49 let mut buf = Vec::new();
50 let mut current_url = None;
51 let mut current_title = None;
52 let mut in_self_uri = false;
53
54 loop {
55 match reader.read_event_into(&mut buf) {
56 Ok(Event::Start(e)) if e.name().as_ref() == b"selfUri" => {
57 in_self_uri = true;
58 }
59 Ok(Event::End(e)) if e.name().as_ref() == b"selfUri" => {
60 in_self_uri = false;
61 if let (Some(url), Some(title)) = (current_url.take(), current_title.take()) {
62 jobs.push(JobSummary {
63 job_id: title,
64 url,
65 });
66 }
67 }
68 Ok(Event::Start(e)) if in_self_uri && e.name().as_ref() == b"url" => {
69 if let Ok(Event::Text(t)) = reader.read_event_into(&mut buf) {
70 current_url = reader.decoder().decode(t.as_ref()).ok().map(|s| s.to_string());
71 }
72 }
73 Ok(Event::Start(e)) if in_self_uri && e.name().as_ref() == b"title" => {
74 if let Ok(Event::Text(t)) = reader.read_event_into(&mut buf) {
75 current_title = reader.decoder().decode(t.as_ref()).ok().map(|s| s.to_string());
76 }
77 }
78 Ok(Event::Eof) => break,
79 Err(e) => return Err(anyhow::anyhow!("XML parse error at position {}: {}", reader.buffer_position(), e)),
80 _ => {}
81 }
82 buf.clear();
83 }
84
85 Ok(jobs)
86}
87
88pub fn parse_job_status(xml: &str) -> Result<JobStatus> {
89 let mut reader = Reader::from_str(xml);
90 reader.config_mut().trim_text(true);
91
92 let mut buf = Vec::new();
93 let mut job_id = String::new();
94 let mut job_stage = String::new();
95 let mut failed = false;
96 let mut date_submitted = None;
97 let mut self_uri = String::new();
98 let mut results_uri = None;
99 let mut messages = Vec::new();
100
101 let mut current_tag = String::new();
102 let mut in_results_uri = false;
103 let mut in_message = false;
104 let mut current_message_stage = String::new();
105 let mut current_message_text = String::new();
106 let mut current_message_timestamp = None;
107
108 loop {
109 match reader.read_event_into(&mut buf) {
110 Ok(Event::Start(e)) => {
111 current_tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
112 match current_tag.as_str() {
113 "resultsUri" => in_results_uri = true,
114 "message" => {
115 in_message = true;
116 current_message_stage.clear();
117 current_message_text.clear();
118 current_message_timestamp = None;
119 }
120 _ => {}
121 }
122 }
123 Ok(Event::End(e)) => {
124 let tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
125 match tag.as_str() {
126 "resultsUri" => in_results_uri = false,
127 "message" => {
128 if in_message {
129 messages.push(JobMessage {
130 stage: current_message_stage.clone(),
131 text: current_message_text.clone(),
132 timestamp: current_message_timestamp.clone(),
133 });
134 in_message = false;
135 }
136 }
137 _ => {}
138 }
139 current_tag.clear();
140 }
141 Ok(Event::Text(e)) => {
142 let text = reader.decoder().decode(e.as_ref())
143 .map(|s| s.to_string())
144 .unwrap_or_default();
145 match current_tag.as_str() {
146 "jobHandle" => job_id = text,
147 "jobStage" => job_stage = text,
148 "failed" => failed = text == "true",
149 "dateSubmitted" => date_submitted = Some(text),
150 "url" if in_results_uri => results_uri = Some(text),
151 "url" if !in_results_uri && self_uri.is_empty() => self_uri = text,
152 "stage" if in_message => current_message_stage = text,
153 "text" if in_message => current_message_text = text,
154 "timestamp" if in_message => current_message_timestamp = Some(text),
155 _ => {}
156 }
157 }
158 Ok(Event::Eof) => break,
159 Err(e) => return Err(anyhow::anyhow!("XML parse error: {}", e)),
160 _ => {}
161 }
162 buf.clear();
163 }
164
165 if job_id.is_empty() {
166 anyhow::bail!("Failed to parse job status: missing job ID");
167 }
168
169 Ok(JobStatus {
170 job_id,
171 job_stage,
172 failed,
173 date_submitted,
174 self_uri,
175 results_uri,
176 messages,
177 })
178}
179
180pub fn parse_output_files(xml: &str) -> Result<Vec<OutputFile>> {
181 let mut reader = Reader::from_str(xml);
182 reader.config_mut().trim_text(true);
183
184 let mut files = Vec::new();
185 let mut buf = Vec::new();
186
187 let mut in_jobfile = false;
188 let mut in_download_uri = false;
189 let mut current_filename = None;
190 let mut current_download_uri = None;
191 let mut current_size = None;
192 let mut current_tag = String::new();
193
194 loop {
195 match reader.read_event_into(&mut buf) {
196 Ok(Event::Start(e)) => {
197 let tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
198 match tag.as_str() {
199 "jobfile" => in_jobfile = true,
200 "downloadUri" => in_download_uri = true,
201 _ => current_tag = tag,
202 }
203 }
204 Ok(Event::End(e)) => {
205 let tag = String::from_utf8_lossy(e.name().as_ref()).to_string();
206 match tag.as_str() {
207 "jobfile" => {
208 if let (Some(filename), Some(download_uri), Some(size)) =
209 (current_filename.take(), current_download_uri.take(), current_size.take())
210 {
211 files.push(OutputFile {
212 filename,
213 download_uri,
214 size,
215 });
216 }
217 in_jobfile = false;
218 }
219 "downloadUri" => in_download_uri = false,
220 _ => {}
221 }
222 current_tag.clear();
223 }
224 Ok(Event::Text(e)) => {
225 let text = reader.decoder().decode(e.as_ref())
226 .map(|s| s.to_string())
227 .unwrap_or_default();
228 if in_jobfile {
229 match current_tag.as_str() {
230 "filename" => current_filename = Some(text),
231 "length" => current_size = text.parse().ok(),
232 "url" if in_download_uri => current_download_uri = Some(text),
233 _ => {}
234 }
235 }
236 }
237 Ok(Event::Eof) => break,
238 Err(e) => return Err(anyhow::anyhow!("XML parse error: {}", e)),
239 _ => {}
240 }
241 buf.clear();
242 }
243
244 Ok(files)
245}