use quick_xml::events::Event; use quick_xml::Reader; #[derive(Debug, Default)] pub struct DeleteObjectsRequest { pub objects: Vec, pub quiet: bool, } #[derive(Debug)] pub struct ObjectIdentifier { pub key: String, pub version_id: Option, } #[derive(Debug, Default)] pub struct CompleteMultipartUpload { pub parts: Vec, } #[derive(Debug)] pub struct CompletedPart { pub part_number: u32, pub etag: String, } pub fn parse_complete_multipart_upload(xml: &str) -> Result { let mut reader = Reader::from_str(xml); let mut result = CompleteMultipartUpload::default(); let mut buf = Vec::new(); let mut current_tag = String::new(); let mut part_number: Option = None; let mut etag: Option = None; let mut in_part = false; loop { match reader.read_event_into(&mut buf) { Ok(Event::Start(ref e)) => { let name = String::from_utf8_lossy(e.name().as_ref()).to_string(); current_tag = name.clone(); if name == "Part" { in_part = true; part_number = None; etag = None; } } Ok(Event::Text(ref e)) => { if in_part { let text = e.unescape().map_err(|e| e.to_string())?.to_string(); match current_tag.as_str() { "PartNumber" => { part_number = Some( text.trim() .parse() .map_err(|e: std::num::ParseIntError| e.to_string())?, ); } "ETag" => { etag = Some(text.trim().trim_matches('"').to_string()); } _ => {} } } } Ok(Event::End(ref e)) => { let name = String::from_utf8_lossy(e.name().as_ref()).to_string(); if name == "Part" && in_part { if let (Some(pn), Some(et)) = (part_number.take(), etag.take()) { result.parts.push(CompletedPart { part_number: pn, etag: et, }); } in_part = false; } } Ok(Event::Eof) => break, Err(e) => return Err(format!("XML parse error: {}", e)), _ => {} } buf.clear(); } result.parts.sort_by_key(|p| p.part_number); Ok(result) } pub fn parse_delete_objects(xml: &str) -> Result { let mut reader = Reader::from_str(xml); let mut result = DeleteObjectsRequest::default(); let mut buf = Vec::new(); let mut current_tag = String::new(); let mut current_key: Option = None; let mut current_version_id: Option = None; let mut in_object = false; loop { match reader.read_event_into(&mut buf) { Ok(Event::Start(ref e)) => { let name = String::from_utf8_lossy(e.name().as_ref()).to_string(); current_tag = name.clone(); if name == "Object" { in_object = true; current_key = None; current_version_id = None; } } Ok(Event::Text(ref e)) => { let text = e.unescape().map_err(|e| e.to_string())?.to_string(); match current_tag.as_str() { "Key" if in_object => { current_key = Some(text.trim().to_string()); } "VersionId" if in_object => { current_version_id = Some(text.trim().to_string()); } "Quiet" => { result.quiet = text.trim() == "true"; } _ => {} } } Ok(Event::End(ref e)) => { let name = String::from_utf8_lossy(e.name().as_ref()).to_string(); if name == "Object" && in_object { if let Some(key) = current_key.take() { result.objects.push(ObjectIdentifier { key, version_id: current_version_id.take(), }); } in_object = false; } } Ok(Event::Eof) => break, Err(e) => return Err(format!("XML parse error: {}", e)), _ => {} } buf.clear(); } Ok(result) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_complete_multipart() { let xml = r#" 2"etag2" 1"etag1" "#; let result = parse_complete_multipart_upload(xml).unwrap(); assert_eq!(result.parts.len(), 2); assert_eq!(result.parts[0].part_number, 1); assert_eq!(result.parts[0].etag, "etag1"); assert_eq!(result.parts[1].part_number, 2); assert_eq!(result.parts[1].etag, "etag2"); } }