use std::ops::Deref;
use super::*;
#[cfg(feature = "serde")]
mod serde_impl;
#[cfg(feature = "serde")]
pub use serde_impl::*;
pub mod prelude
{
#[cfg(feature = "serde")]
pub use super::serde_impl::{UrlDeserializer, UrlSerializer};
pub use super::{FromUrl, MediaType, ToUrl};
}
#[derive(Debug, PartialEq, Eq)]
pub struct UrlDataMeta<'a>
{
pub scheme: &'a str,
pub media_type: &'a str,
pub extension: &'a str,
pub encoding: Option<&'a str>,
}
impl<'a> TryFrom<&'a str> for UrlDataMeta<'a>
{
type Error = EncodeError;
fn try_from(value: &'a str) -> Result<Self, Self::Error>
{
let value = value.trim();
let (scheme, rest) = value
.split_once(':')
.ok_or_else(|| EncodeError::custom("URL must have a scheme"))?;
let meta = rest.split(',').next().unwrap_or(rest);
let (media_type_and_ext, encoding) = match meta.split_once(';')
{
Some((m, e)) => (m, Some(e)),
None => (meta, None),
};
let (media_type, extension) = media_type_and_ext
.split_once('/')
.ok_or_else(|| EncodeError::custom("Invalid media type in URL"))?;
Ok(UrlDataMeta {
scheme,
media_type,
extension,
encoding,
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct UrlData<'a>
{
pub meta: UrlDataMeta<'a>,
pub data: &'a str,
}
const MIN_BYTE_SEPARATOR_SEARCH: usize = 256;
impl<'a> Deref for UrlData<'a>
{
type Target = UrlDataMeta<'a>;
fn deref(&self) -> &Self::Target { &self.meta }
}
impl<'a> TryFrom<&'a str> for UrlData<'a>
{
type Error = EncodeError;
fn try_from(value: &'a str) -> Result<Self, Self::Error>
{
let search_len = value.len().min(MIN_BYTE_SEPARATOR_SEARCH);
let prefix = &value[..search_len];
let comma_pos = prefix.find(',').ok_or_else(|| {
EncodeError::custom(format!(
"Missing ',' separator in URL (first {MIN_BYTE_SEPARATOR_SEARCH} bytes)"
))
})?;
let (meta_str, data) = value.split_at(comma_pos);
let data = &data[1..];
let meta = UrlDataMeta::try_from(meta_str)?;
if meta.scheme != "data"
{
return Err(EncodeError::custom("Invalid URL scheme: expected 'data'"));
}
Ok(UrlData { meta, data })
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct BinUrlData<'a>
{
pub meta: UrlDataMeta<'a>,
pub data: &'a [u8],
}
impl<'a> Deref for BinUrlData<'a>
{
type Target = UrlDataMeta<'a>;
fn deref(&self) -> &Self::Target { &self.meta }
}
impl<'a> TryFrom<&'a [u8]> for BinUrlData<'a>
{
type Error = EncodeError;
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error>
{
let search_len = value.len().min(MIN_BYTE_SEPARATOR_SEARCH);
let prefix = &value[..search_len];
let comma_pos = prefix.iter().position(|&b| b == b',').ok_or_else(|| {
EncodeError::custom(format!(
"Missing ',' separator in Bin URL (first {MIN_BYTE_SEPARATOR_SEARCH} bytes)"
))
})?;
let meta_bytes = &value[..comma_pos];
let data = &value[comma_pos + 1..];
let meta_str = std::str::from_utf8(meta_bytes)
.map_err(|_| EncodeError::custom("Invalid UTF-8 in metadata"))?;
let meta = UrlDataMeta::try_from(meta_str)?;
if meta.scheme != "bin_data"
{
return Err(EncodeError::custom(
"Invalid URL scheme: expected 'bin_data'",
));
}
Ok(BinUrlData { meta, data })
}
}
pub trait MediaType
{
fn media_type() -> &'static str;
fn mime_type(extension: &extension) -> String
{
let media = Self::media_type();
format!("{media}/{extension}")
}
}
impl MediaType for String
{
fn media_type() -> &'static str { "text" }
}
impl<'a> MediaType for &'a str
{
fn media_type() -> &'static str { String::media_type() }
}
pub trait ToUrl: MediaType + SaveExtension
{
fn to_url(&self, extension: &extension) -> EncodeResult<String>
{
let (bytes, _deduced_extension) = self.save_to_bytes(extension)?;
let media = Self::media_type();
let url = bytes.to_base64_in(format!("data:{media}/{extension};base64,"));
Ok(url)
}
fn to_url_bin(&self, extension: &extension) -> EncodeResult<Vec<u8>>
{
let media = Self::media_type();
let mut data = Vec::with_capacity(1024);
write!(&mut data, "bin_data:{media}/{extension};base64,")
.map_err(|e| EncodeError::from(e))?;
let (data, _deduced_extension) = self.save_to_bytes_in(data, extension)?;
Ok(data)
}
}
impl<T> ToUrl for T where T: MediaType + SaveExtension {}
pub trait FromUrl: LoadExtension
{
fn from_url(url: &str) -> EncodeResult<Self>
where
Self: Sized,
{
let url = UrlData::try_from(url)?;
let bytes = Vec::<u8>::from_base64(url.data)?;
Self::load_from_bytes_with_custom_extension(&bytes, url.extension)
}
fn from_bin_url(url: &[u8]) -> EncodeResult<Self>
where
Self: Sized,
{
let url = BinUrlData::try_from(url)?;
Self::load_from_bytes_with_custom_extension(&url.data, url.extension)
}
fn from_bin_url_or_bytes(bytes: &[u8], extension: &extension) -> EncodeResult<Self>
where
Self: Sized,
{
match Self::from_bin_url(bytes)
{
Ok(o) => Ok(o),
Err(_) => Self::load_from_bytes_with_custom_extension(bytes, extension),
}
}
}
impl<T> FromUrl for T where T: LoadExtension {}