use serde::{Deserialize, Serialize};
use crate::sql::{SqlFormat, ToSql};
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct File {
pub bucket: String,
pub key: String,
}
impl File {
pub fn new<B: Into<String>, K: Into<String>>(bucket: B, key: K) -> Self {
let bucket: String = bucket.into();
let key: String = key.into();
let key = if key.starts_with("/") {
key
} else {
format!("/{key}")
};
Self {
bucket,
key,
}
}
pub fn bucket(&self) -> &str {
&self.bucket
}
pub fn key(&self) -> &str {
&self.key
}
}
impl ToSql for crate::File {
fn fmt_sql(&self, f: &mut String, _fmt: SqlFormat) {
f.push_str("f\"");
f.push_str(&fmt_inner(&self.bucket, true));
f.push(':');
f.push_str(&fmt_inner(&self.key, false));
f.push('"');
}
}
fn fmt_inner(v: &str, escape_slash: bool) -> String {
v.chars()
.flat_map(|c| {
if c.is_ascii_alphanumeric()
|| matches!(c, '-' | '_' | '.')
|| (!escape_slash && c == '/')
{
vec![c]
} else {
vec!['\\', c]
}
})
.collect::<String>()
}
#[cfg(feature = "arbitrary")]
mod arb {
use super::*;
impl<'a> arbitrary::Arbitrary<'a> for File {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
static CHAR: [u8; 56] = [
b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n',
b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'A', b'B',
b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P',
b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'_', b'-', b'.', b'/',
];
let mut bucket = String::new();
bucket.push(CHAR[u.int_in_range(0u8..=54)? as usize] as char);
for _ in 0..u.arbitrary_len::<u8>()? {
bucket.push(CHAR[u.int_in_range(0u8..=54)? as usize] as char);
}
let mut key = "/".to_string();
for _ in 0..u.arbitrary_len::<u8>()? {
key.push(CHAR[u.int_in_range(0u8..=55)? as usize] as char);
}
Ok(File {
bucket,
key,
})
}
}
}