rusty-cat 0.2.0

Async HTTP client for resumable file upload and download.
Documentation
pub(crate) fn extract_xml_tag(body: &str, tag: &str) -> Option<String> {
    let open = format!("<{tag}>");
    let close = format!("</{tag}>");
    let (_, tail) = body.split_once(open.as_str())?;
    let (value, _) = tail.split_once(close.as_str())?;
    Some(value.trim().to_string())
}

pub(crate) fn extract_upload_ids_for_key(xml: &str, key: &str) -> Vec<String> {
    let mut ids = Vec::new();
    for seg in xml.split("<Upload>").skip(1) {
        if let Some((upload_block, _)) = seg.split_once("</Upload>") {
            let item_key = extract_xml_tag(upload_block, "Key");
            let upload_id = extract_xml_tag(upload_block, "UploadId");
            if item_key.as_deref() == Some(key) {
                if let Some(id) = upload_id {
                    ids.push(id);
                }
            }
        }
    }
    ids
}

pub(crate) fn extract_part_numbers(xml: &str) -> Vec<u64> {
    let mut out = Vec::new();
    for seg in xml.split("<PartNumber>").skip(1) {
        if let Some((value, _)) = seg.split_once("</PartNumber>") {
            if let Ok(part_number) = value.trim().parse::<u64>() {
                out.push(part_number);
            }
        }
    }
    out.sort_unstable();
    out.dedup();
    out
}

#[cfg(test)]
mod tests {
    use super::{extract_part_numbers, extract_upload_ids_for_key, extract_xml_tag};

    #[test]
    fn test_extract_xml_tag_trims_value() {
        let xml = "<Root><UploadId>  abc-123  </UploadId></Root>";
        assert_eq!(extract_xml_tag(xml, "UploadId").as_deref(), Some("abc-123"));
    }

    #[test]
    fn test_extract_upload_ids_for_key_filters_target_key() {
        let xml = "<ListMultipartUploadsResult><Upload><Key>target/a.bin</Key><UploadId>id-1</UploadId></Upload><Upload><Key>other.bin</Key><UploadId>id-other</UploadId></Upload></ListMultipartUploadsResult>";
        assert_eq!(
            extract_upload_ids_for_key(xml, "target/a.bin"),
            vec!["id-1".to_string()]
        );
    }

    #[test]
    fn test_extract_part_numbers_sorts_and_dedups() {
        let xml = "<ListPartsResult><Part><PartNumber>3</PartNumber></Part><Part><PartNumber>1</PartNumber></Part><Part><PartNumber>3</PartNumber></Part></ListPartsResult>";
        assert_eq!(extract_part_numbers(xml), vec![1, 3]);
    }
}