610 lines
20 KiB
Rust
610 lines
20 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use myfsio_common::types::{BucketMeta, ObjectMeta};
|
|
use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
|
|
use quick_xml::Writer;
|
|
use std::io::Cursor;
|
|
|
|
pub fn format_s3_datetime(dt: &DateTime<Utc>) -> String {
|
|
dt.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string()
|
|
}
|
|
|
|
pub fn rate_limit_exceeded_xml(resource: &str, request_id: &str) -> String {
|
|
format!(
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
|
|
<Error><Code>SlowDown</Code><Message>Please reduce your request rate</Message><Resource>{}</Resource><RequestId>{}</RequestId></Error>",
|
|
xml_escape(resource),
|
|
xml_escape(request_id),
|
|
)
|
|
}
|
|
|
|
fn xml_escape(s: &str) -> String {
|
|
s.replace('&', "&")
|
|
.replace('<', "<")
|
|
.replace('>', ">")
|
|
.replace('"', """)
|
|
.replace('\'', "'")
|
|
}
|
|
|
|
pub fn list_buckets_xml(owner_id: &str, owner_name: &str, buckets: &[BucketMeta]) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("ListAllMyBucketsResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("Owner")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "ID", owner_id);
|
|
write_text_element(&mut writer, "DisplayName", owner_name);
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("Owner")))
|
|
.unwrap();
|
|
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("Buckets")))
|
|
.unwrap();
|
|
for bucket in buckets {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("Bucket")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "Name", &bucket.name);
|
|
write_text_element(
|
|
&mut writer,
|
|
"CreationDate",
|
|
&format_s3_datetime(&bucket.creation_date),
|
|
);
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("Bucket")))
|
|
.unwrap();
|
|
}
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("Buckets")))
|
|
.unwrap();
|
|
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("ListAllMyBucketsResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
fn maybe_url_encode(value: &str, encoding_type: Option<&str>) -> String {
|
|
if matches!(encoding_type, Some(v) if v.eq_ignore_ascii_case("url")) {
|
|
percent_encoding::utf8_percent_encode(value, KEY_ENCODE_SET).to_string()
|
|
} else {
|
|
value.to_string()
|
|
}
|
|
}
|
|
|
|
const KEY_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC
|
|
.remove(b'-')
|
|
.remove(b'_')
|
|
.remove(b'.')
|
|
.remove(b'~')
|
|
.remove(b'/');
|
|
|
|
pub fn list_objects_v2_xml(
|
|
bucket_name: &str,
|
|
prefix: &str,
|
|
delimiter: &str,
|
|
max_keys: usize,
|
|
objects: &[ObjectMeta],
|
|
common_prefixes: &[String],
|
|
is_truncated: bool,
|
|
continuation_token: Option<&str>,
|
|
next_continuation_token: Option<&str>,
|
|
key_count: usize,
|
|
) -> String {
|
|
list_objects_v2_xml_with_encoding(
|
|
bucket_name,
|
|
prefix,
|
|
delimiter,
|
|
max_keys,
|
|
objects,
|
|
common_prefixes,
|
|
is_truncated,
|
|
continuation_token,
|
|
next_continuation_token,
|
|
key_count,
|
|
None,
|
|
)
|
|
}
|
|
|
|
pub fn list_objects_v2_xml_with_encoding(
|
|
bucket_name: &str,
|
|
prefix: &str,
|
|
delimiter: &str,
|
|
max_keys: usize,
|
|
objects: &[ObjectMeta],
|
|
common_prefixes: &[String],
|
|
is_truncated: bool,
|
|
continuation_token: Option<&str>,
|
|
next_continuation_token: Option<&str>,
|
|
key_count: usize,
|
|
encoding_type: Option<&str>,
|
|
) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("ListBucketResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
|
|
write_text_element(&mut writer, "Name", bucket_name);
|
|
write_text_element(&mut writer, "Prefix", &maybe_url_encode(prefix, encoding_type));
|
|
if !delimiter.is_empty() {
|
|
write_text_element(
|
|
&mut writer,
|
|
"Delimiter",
|
|
&maybe_url_encode(delimiter, encoding_type),
|
|
);
|
|
}
|
|
write_text_element(&mut writer, "MaxKeys", &max_keys.to_string());
|
|
write_text_element(&mut writer, "KeyCount", &key_count.to_string());
|
|
write_text_element(&mut writer, "IsTruncated", &is_truncated.to_string());
|
|
if let Some(encoding) = encoding_type {
|
|
if !encoding.is_empty() {
|
|
write_text_element(&mut writer, "EncodingType", encoding);
|
|
}
|
|
}
|
|
|
|
if let Some(token) = continuation_token {
|
|
write_text_element(&mut writer, "ContinuationToken", token);
|
|
}
|
|
if let Some(token) = next_continuation_token {
|
|
write_text_element(&mut writer, "NextContinuationToken", token);
|
|
}
|
|
|
|
for obj in objects {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("Contents")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "Key", &maybe_url_encode(&obj.key, encoding_type));
|
|
write_text_element(
|
|
&mut writer,
|
|
"LastModified",
|
|
&format_s3_datetime(&obj.last_modified),
|
|
);
|
|
if let Some(ref etag) = obj.etag {
|
|
write_text_element(&mut writer, "ETag", &format!("\"{}\"", etag));
|
|
}
|
|
write_text_element(&mut writer, "Size", &obj.size.to_string());
|
|
write_text_element(
|
|
&mut writer,
|
|
"StorageClass",
|
|
obj.storage_class.as_deref().unwrap_or("STANDARD"),
|
|
);
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("Contents")))
|
|
.unwrap();
|
|
}
|
|
|
|
for prefix in common_prefixes {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("CommonPrefixes")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "Prefix", &maybe_url_encode(prefix, encoding_type));
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("CommonPrefixes")))
|
|
.unwrap();
|
|
}
|
|
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("ListBucketResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
pub fn list_objects_v1_xml(
|
|
bucket_name: &str,
|
|
prefix: &str,
|
|
marker: &str,
|
|
delimiter: &str,
|
|
max_keys: usize,
|
|
objects: &[ObjectMeta],
|
|
common_prefixes: &[String],
|
|
is_truncated: bool,
|
|
next_marker: Option<&str>,
|
|
) -> String {
|
|
list_objects_v1_xml_with_encoding(
|
|
bucket_name,
|
|
prefix,
|
|
marker,
|
|
delimiter,
|
|
max_keys,
|
|
objects,
|
|
common_prefixes,
|
|
is_truncated,
|
|
next_marker,
|
|
None,
|
|
)
|
|
}
|
|
|
|
pub fn list_objects_v1_xml_with_encoding(
|
|
bucket_name: &str,
|
|
prefix: &str,
|
|
marker: &str,
|
|
delimiter: &str,
|
|
max_keys: usize,
|
|
objects: &[ObjectMeta],
|
|
common_prefixes: &[String],
|
|
is_truncated: bool,
|
|
next_marker: Option<&str>,
|
|
encoding_type: Option<&str>,
|
|
) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("ListBucketResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
|
|
write_text_element(&mut writer, "Name", bucket_name);
|
|
write_text_element(&mut writer, "Prefix", &maybe_url_encode(prefix, encoding_type));
|
|
write_text_element(&mut writer, "Marker", &maybe_url_encode(marker, encoding_type));
|
|
write_text_element(&mut writer, "MaxKeys", &max_keys.to_string());
|
|
write_text_element(&mut writer, "IsTruncated", &is_truncated.to_string());
|
|
|
|
if !delimiter.is_empty() {
|
|
write_text_element(
|
|
&mut writer,
|
|
"Delimiter",
|
|
&maybe_url_encode(delimiter, encoding_type),
|
|
);
|
|
}
|
|
if !delimiter.is_empty() && is_truncated {
|
|
if let Some(nm) = next_marker {
|
|
if !nm.is_empty() {
|
|
write_text_element(&mut writer, "NextMarker", &maybe_url_encode(nm, encoding_type));
|
|
}
|
|
}
|
|
}
|
|
if let Some(encoding) = encoding_type {
|
|
if !encoding.is_empty() {
|
|
write_text_element(&mut writer, "EncodingType", encoding);
|
|
}
|
|
}
|
|
|
|
for obj in objects {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("Contents")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "Key", &maybe_url_encode(&obj.key, encoding_type));
|
|
write_text_element(
|
|
&mut writer,
|
|
"LastModified",
|
|
&format_s3_datetime(&obj.last_modified),
|
|
);
|
|
if let Some(ref etag) = obj.etag {
|
|
write_text_element(&mut writer, "ETag", &format!("\"{}\"", etag));
|
|
}
|
|
write_text_element(&mut writer, "Size", &obj.size.to_string());
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("Contents")))
|
|
.unwrap();
|
|
}
|
|
|
|
for cp in common_prefixes {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("CommonPrefixes")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "Prefix", &maybe_url_encode(cp, encoding_type));
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("CommonPrefixes")))
|
|
.unwrap();
|
|
}
|
|
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("ListBucketResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
fn write_text_element(writer: &mut Writer<Cursor<Vec<u8>>>, tag: &str, text: &str) {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new(tag)))
|
|
.unwrap();
|
|
writer
|
|
.write_event(Event::Text(BytesText::new(text)))
|
|
.unwrap();
|
|
writer.write_event(Event::End(BytesEnd::new(tag))).unwrap();
|
|
}
|
|
|
|
pub fn initiate_multipart_upload_xml(bucket: &str, key: &str, upload_id: &str) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("InitiateMultipartUploadResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
write_text_element(&mut writer, "Bucket", bucket);
|
|
write_text_element(&mut writer, "Key", key);
|
|
write_text_element(&mut writer, "UploadId", upload_id);
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("InitiateMultipartUploadResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
pub fn complete_multipart_upload_xml(
|
|
bucket: &str,
|
|
key: &str,
|
|
etag: &str,
|
|
location: &str,
|
|
) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("CompleteMultipartUploadResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
write_text_element(&mut writer, "Location", location);
|
|
write_text_element(&mut writer, "Bucket", bucket);
|
|
write_text_element(&mut writer, "Key", key);
|
|
write_text_element(&mut writer, "ETag", &format!("\"{}\"", etag));
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("CompleteMultipartUploadResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
pub fn copy_part_result_xml(etag: &str, last_modified: &str) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("CopyPartResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
write_text_element(&mut writer, "LastModified", last_modified);
|
|
write_text_element(&mut writer, "ETag", &format!("\"{}\"", etag));
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("CopyPartResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
pub fn post_object_result_xml(location: &str, bucket: &str, key: &str, etag: &str) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("PostResponse")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
write_text_element(&mut writer, "Location", location);
|
|
write_text_element(&mut writer, "Bucket", bucket);
|
|
write_text_element(&mut writer, "Key", key);
|
|
write_text_element(&mut writer, "ETag", &format!("\"{}\"", etag));
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("PostResponse")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
pub fn copy_object_result_xml(etag: &str, last_modified: &str) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("CopyObjectResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
write_text_element(&mut writer, "ETag", &format!("\"{}\"", etag));
|
|
write_text_element(&mut writer, "LastModified", last_modified);
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("CopyObjectResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
pub struct DeletedEntry {
|
|
pub key: String,
|
|
pub version_id: Option<String>,
|
|
pub delete_marker: bool,
|
|
pub delete_marker_version_id: Option<String>,
|
|
}
|
|
|
|
pub fn delete_result_xml(
|
|
deleted: &[DeletedEntry],
|
|
errors: &[(String, String, String)],
|
|
quiet: bool,
|
|
) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("DeleteResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
|
|
if !quiet {
|
|
for entry in deleted {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("Deleted")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "Key", &entry.key);
|
|
if let Some(ref vid) = entry.version_id {
|
|
write_text_element(&mut writer, "VersionId", vid);
|
|
}
|
|
if entry.delete_marker {
|
|
write_text_element(&mut writer, "DeleteMarker", "true");
|
|
if let Some(ref dm_vid) = entry.delete_marker_version_id {
|
|
write_text_element(&mut writer, "DeleteMarkerVersionId", dm_vid);
|
|
}
|
|
}
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("Deleted")))
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
for (key, code, message) in errors {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("Error")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "Key", key);
|
|
write_text_element(&mut writer, "Code", code);
|
|
write_text_element(&mut writer, "Message", message);
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("Error")))
|
|
.unwrap();
|
|
}
|
|
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("DeleteResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
pub fn list_multipart_uploads_xml(
|
|
bucket: &str,
|
|
uploads: &[myfsio_common::types::MultipartUploadInfo],
|
|
) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("ListMultipartUploadsResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
write_text_element(&mut writer, "Bucket", bucket);
|
|
|
|
for upload in uploads {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("Upload")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "Key", &upload.key);
|
|
write_text_element(&mut writer, "UploadId", &upload.upload_id);
|
|
write_text_element(
|
|
&mut writer,
|
|
"Initiated",
|
|
&format_s3_datetime(&upload.initiated),
|
|
);
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("Upload")))
|
|
.unwrap();
|
|
}
|
|
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("ListMultipartUploadsResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
pub fn list_parts_xml(
|
|
bucket: &str,
|
|
key: &str,
|
|
upload_id: &str,
|
|
parts: &[myfsio_common::types::PartMeta],
|
|
) -> String {
|
|
let mut writer = Writer::new(Cursor::new(Vec::new()));
|
|
writer
|
|
.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))
|
|
.unwrap();
|
|
|
|
let start = BytesStart::new("ListPartsResult")
|
|
.with_attributes([("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")]);
|
|
writer.write_event(Event::Start(start)).unwrap();
|
|
write_text_element(&mut writer, "Bucket", bucket);
|
|
write_text_element(&mut writer, "Key", key);
|
|
write_text_element(&mut writer, "UploadId", upload_id);
|
|
|
|
for part in parts {
|
|
writer
|
|
.write_event(Event::Start(BytesStart::new("Part")))
|
|
.unwrap();
|
|
write_text_element(&mut writer, "PartNumber", &part.part_number.to_string());
|
|
write_text_element(&mut writer, "ETag", &format!("\"{}\"", part.etag));
|
|
write_text_element(&mut writer, "Size", &part.size.to_string());
|
|
if let Some(ref lm) = part.last_modified {
|
|
write_text_element(&mut writer, "LastModified", &format_s3_datetime(lm));
|
|
}
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("Part")))
|
|
.unwrap();
|
|
}
|
|
|
|
writer
|
|
.write_event(Event::End(BytesEnd::new("ListPartsResult")))
|
|
.unwrap();
|
|
|
|
String::from_utf8(writer.into_inner().into_inner()).unwrap()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use chrono::Utc;
|
|
|
|
#[test]
|
|
fn test_list_buckets_xml() {
|
|
let buckets = vec![BucketMeta {
|
|
name: "test-bucket".to_string(),
|
|
creation_date: Utc::now(),
|
|
}];
|
|
let xml = list_buckets_xml("owner-id", "owner-name", &buckets);
|
|
assert!(xml.contains("<Name>test-bucket</Name>"));
|
|
assert!(xml.contains("<ID>owner-id</ID>"));
|
|
assert!(xml.contains("ListAllMyBucketsResult"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_list_objects_v2_xml() {
|
|
let objects = vec![ObjectMeta::new("file.txt".to_string(), 1024, Utc::now())];
|
|
let xml = list_objects_v2_xml(
|
|
"my-bucket",
|
|
"",
|
|
"/",
|
|
1000,
|
|
&objects,
|
|
&[],
|
|
false,
|
|
None,
|
|
None,
|
|
1,
|
|
);
|
|
assert!(xml.contains("<Key>file.txt</Key>"));
|
|
assert!(xml.contains("<Size>1024</Size>"));
|
|
assert!(xml.contains("<IsTruncated>false</IsTruncated>"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_list_objects_v1_xml() {
|
|
let objects = vec![ObjectMeta::new("file.txt".to_string(), 1024, Utc::now())];
|
|
let xml = list_objects_v1_xml("my-bucket", "", "", "/", 1000, &objects, &[], false, None);
|
|
assert!(xml.contains("<Key>file.txt</Key>"));
|
|
assert!(xml.contains("<Size>1024</Size>"));
|
|
assert!(xml.contains("<Marker></Marker>"));
|
|
}
|
|
}
|