use crate::{imp::RiMaybeRef, Iri, IriRef, Uri, UriRef};
use borrow_or_share::Bos;
use core::str;
#[cfg(feature = "alloc")]
use crate::{
imp::{HostMeta, Meta, RmrRef},
pct_enc,
};
#[cfg(feature = "alloc")]
use alloc::string::String;
#[cfg(feature = "alloc")]
use core::num::NonZeroUsize;
macro_rules! impl_from {
($($x:ident => $($y:ident),+)*) => {
$($(
impl<T: Bos<str>> From<$x<T>> for $y<T> {
#[doc = concat!("Consumes the `", stringify!($x), "` and creates a new [`", stringify!($y), "`] with the same contents.")]
fn from(value: $x<T>) -> Self {
RiMaybeRef::new(value.val, value.meta)
}
}
)+)*
};
}
impl_from! {
Uri => UriRef, Iri, IriRef
UriRef => IriRef
Iri => IriRef
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConvertError {
NotAscii {
index: usize,
},
NoScheme,
}
#[cfg(feature = "impl-error")]
impl crate::Error for ConvertError {}
macro_rules! impl_try_from {
($(#[$doc:meta] $x:ident if $($cond:ident)&&+ => $y:ident)*) => {
$(
impl<'a> TryFrom<$x<&'a str>> for $y<&'a str> {
type Error = ConvertError;
#[$doc]
fn try_from(value: $x<&'a str>) -> Result<Self, Self::Error> {
let r = value.make_ref();
$(r.$cond()?;)+
Ok(RiMaybeRef::new(value.val, value.meta))
}
}
#[cfg(feature = "alloc")]
impl TryFrom<$x<String>> for $y<String> {
type Error = (ConvertError, $x<String>);
#[$doc]
fn try_from(value: $x<String>) -> Result<Self, Self::Error> {
let r = value.make_ref();
$(
if let Err(e) = r.$cond() {
return Err((e, value));
}
)+
Ok(RiMaybeRef::new(value.val, value.meta))
}
}
)*
};
}
impl_try_from! {
UriRef if ensure_has_scheme => Uri
Iri if ensure_ascii => Uri
IriRef if ensure_has_scheme && ensure_ascii => Uri
IriRef if ensure_ascii => UriRef
IriRef if ensure_has_scheme => Iri
}
#[cfg(feature = "alloc")]
impl<T: Bos<str>> Iri<T> {
pub fn to_uri(&self) -> Uri<String> {
RiMaybeRef::from_pair(encode_non_ascii(self.make_ref()))
}
}
#[cfg(feature = "alloc")]
impl<T: Bos<str>> IriRef<T> {
pub fn to_uri_ref(&self) -> UriRef<String> {
RiMaybeRef::from_pair(encode_non_ascii(self.make_ref()))
}
}
#[cfg(feature = "alloc")]
fn encode_non_ascii(r: RmrRef<'_, '_>) -> (String, Meta) {
let len = r
.as_str()
.chars()
.map(|c| if c.is_ascii() { 1 } else { c.len_utf8() * 3 })
.sum();
let mut buf = String::with_capacity(len);
let mut meta = Meta::default();
if let Some(scheme) = r.scheme_opt() {
buf.push_str(scheme.as_str());
meta.scheme_end = NonZeroUsize::new(buf.len());
buf.push(':');
}
if let Some(auth) = r.authority() {
buf.push_str("//");
if let Some(userinfo) = auth.userinfo() {
encode_non_ascii_str(&mut buf, userinfo.as_str());
buf.push('@');
}
let mut auth_meta = auth.meta();
auth_meta.host_bounds.0 = buf.len();
match auth_meta.host_meta {
HostMeta::RegName => encode_non_ascii_str(&mut buf, auth.host()),
_ => buf.push_str(auth.host()),
}
auth_meta.host_bounds.1 = buf.len();
meta.auth_meta = Some(auth_meta);
if let Some(port) = auth.port() {
buf.push(':');
buf.push_str(port.as_str());
}
}
meta.path_bounds.0 = buf.len();
encode_non_ascii_str(&mut buf, r.path().as_str());
meta.path_bounds.1 = buf.len();
if let Some(query) = r.query() {
buf.push('?');
encode_non_ascii_str(&mut buf, query.as_str());
meta.query_end = NonZeroUsize::new(buf.len());
}
if let Some(fragment) = r.fragment() {
buf.push('#');
encode_non_ascii_str(&mut buf, fragment.as_str());
}
debug_assert_eq!(buf.len(), len);
(buf, meta)
}
#[cfg(feature = "alloc")]
fn encode_non_ascii_str(buf: &mut String, s: &str) {
if s.is_ascii() {
buf.push_str(s);
} else {
let mut iter = s.char_indices();
while let Some((start, ch)) = iter.next() {
if ch.is_ascii() {
buf.push(ch);
} else {
let end = iter.as_str().as_ptr() as usize - s.as_ptr() as usize;
for &x in &s.as_bytes()[start..end] {
buf.push_str(pct_enc::encode_byte(x));
}
}
}
}
}