#![warn(missing_docs)]
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
extern crate opaque_typedef;
#[macro_use]
extern crate opaque_typedef_macros;
extern crate url;
use opaque_typedef::{OpaqueTypedef, OpaqueTypedefUnsized};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub use url::ParseError as UrlParseError;
pub use url::Url;
#[derive(Debug, Clone)]
pub struct AbsoluteIri {
raw: AbsoluteIriString,
resolved: Url,
}
impl AbsoluteIri {
pub fn new<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, UrlParseError> {
let resolved = Url::parse(s.as_ref())?;
let raw = AbsoluteIriString(s.into());
Ok(AbsoluteIri { raw, resolved })
}
pub fn raw(&self) -> &AbsoluteIriStr {
self.raw.as_iri_str()
}
pub fn resolved(&self) -> &Url {
&self.resolved
}
pub fn deconstruct(self) -> (AbsoluteIriString, Url) {
(self.raw, self.resolved)
}
}
#[cfg(feature = "serde")]
impl Serialize for AbsoluteIri {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self.raw())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for AbsoluteIri {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
AbsoluteIri::new(s).map_err(serde::de::Error::custom)
}
}
fn validate_iri_str<S: AsRef<str>>(s: S) -> Result<S, UrlParseError> {
let _ = Url::parse(s.as_ref())?;
Ok(s)
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, OpaqueTypedefUnsized)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[repr(C)]
#[opaque_typedef(
derive(
AsRef(Deref, Self_),
Deref,
Display,
Into(Arc, Box, Rc, Inner),
PartialEq(Inner, InnerRev, InnerCow, SelfCow, SelfCowRev),
PartialOrd(Inner, InnerRev, InnerCow, SelfCow, SelfCowRev)
)
)]
#[opaque_typedef(
validation(
validator = "validate_iri_str",
error_type = "UrlParseError",
error_msg = "Failed to create `AbsoluteIriString`"
)
)]
pub struct AbsoluteIriStr(str);
impl AbsoluteIriStr {
pub fn new(s: &str) -> Result<&AbsoluteIriStr, UrlParseError> {
<Self as OpaqueTypedefUnsized>::try_from_inner(s)
}
pub unsafe fn from_str_unchecked(s: &str) -> &Self {
<Self as OpaqueTypedefUnsized>::from_inner_unchecked(s)
}
fn from_str_unchecked_implicitly_unsafe(s: &str) -> &Self {
let iri = unsafe {
<Self as OpaqueTypedefUnsized>::from_inner_unchecked(s)
};
debug_assert!(
validate_iri_str(s).is_ok(),
"Valid IRI string should be passed to \
`AbsoluteIriStr::from_str_unchecked_implicitly_unsafe()`, but got {:?}",
s
);
iri
}
pub fn to_uri(&self) -> Url {
Url::parse(&self.0).unwrap_or_else(|e| {
unreachable!(
"`AbsoluteIriString` should have valid IRI string, but parsing failed: {}",
e
)
})
}
}
impl ToOwned for AbsoluteIriStr {
type Owned = AbsoluteIriString;
fn to_owned(&self) -> Self::Owned {
unsafe {
<AbsoluteIriString as OpaqueTypedef>::from_inner_unchecked(self.0.to_owned())
}
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for &'de AbsoluteIriStr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
AbsoluteIriStr::new(s).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, OpaqueTypedef)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[opaque_typedef(
derive(
AsRef(Deref, Inner),
Deref,
Display,
IntoInner,
PartialEq(Inner, InnerRev),
PartialOrd(Inner, InnerRev)
)
)]
#[opaque_typedef(
deref(target = "AbsoluteIriStr", deref = "AbsoluteIriStr::from_str_unchecked_implicitly_unsafe")
)]
#[opaque_typedef(
validation(
validator = "validate_iri_str",
error_type = "UrlParseError",
error_msg = "Failed to create `AbsoluteIriString`"
)
)]
pub struct AbsoluteIriString(String);
impl AbsoluteIriString {
pub fn new(s: String) -> Result<AbsoluteIriString, UrlParseError> {
<Self as OpaqueTypedef>::try_from_inner(s)
}
pub fn as_iri_str(&self) -> &AbsoluteIriStr {
self.as_ref()
}
pub fn to_uri(&self) -> Url {
Url::parse(&self.0).unwrap_or_else(|e| {
unreachable!(
"`AbsoluteIriString` should have valid IRI string, but parsing failed: {}",
e
)
})
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl ::std::borrow::Borrow<AbsoluteIriStr> for AbsoluteIriString {
fn borrow(&self) -> &AbsoluteIriStr {
self.as_ref()
}
}
impl AsRef<str> for AbsoluteIriString {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for AbsoluteIriString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
AbsoluteIriString::new(s).map_err(serde::de::Error::custom)
}
}
macro_rules! impl_cmp {
($Lhs:ty, $Rhs:ty) => {
impl PartialEq<$Rhs> for $Lhs {
fn eq(&self, rhs: &$Rhs) -> bool {
AsRef::<str>::as_ref(self).eq(AsRef::<str>::as_ref(rhs))
}
}
impl PartialEq<$Lhs> for $Rhs {
fn eq(&self, rhs: &$Lhs) -> bool {
AsRef::<str>::as_ref(self).eq(AsRef::<str>::as_ref(rhs))
}
}
impl PartialOrd<$Rhs> for $Lhs {
fn partial_cmp(&self, rhs: &$Rhs) -> Option<::std::cmp::Ordering> {
AsRef::<str>::as_ref(self).partial_cmp(AsRef::<str>::as_ref(rhs))
}
}
impl PartialOrd<$Lhs> for $Rhs {
fn partial_cmp(&self, rhs: &$Lhs) -> Option<::std::cmp::Ordering> {
AsRef::<str>::as_ref(self).partial_cmp(AsRef::<str>::as_ref(rhs))
}
}
};
($Lhs:ty, $Rhs:ty, $($lts:tt)*) => {
impl<$($lts)*> PartialEq<$Rhs> for $Lhs {
fn eq(&self, rhs: &$Rhs) -> bool {
AsRef::<str>::as_ref(self).eq(AsRef::<str>::as_ref(rhs))
}
}
impl<$($lts)*> PartialEq<$Lhs> for $Rhs {
fn eq(&self, rhs: &$Lhs) -> bool {
AsRef::<str>::as_ref(self).eq(AsRef::<str>::as_ref(rhs))
}
}
impl<$($lts)*> PartialOrd<$Rhs> for $Lhs {
fn partial_cmp(&self, rhs: &$Rhs) -> Option<::std::cmp::Ordering> {
AsRef::<str>::as_ref(self).partial_cmp(AsRef::<str>::as_ref(rhs))
}
}
impl<$($lts)*> PartialOrd<$Lhs> for $Rhs {
fn partial_cmp(&self, rhs: &$Lhs) -> Option<::std::cmp::Ordering> {
AsRef::<str>::as_ref(self).partial_cmp(AsRef::<str>::as_ref(rhs))
}
}
};
}
impl_cmp!(&'a AbsoluteIriStr, AbsoluteIriStr, 'a);
impl_cmp!(AbsoluteIriString, AbsoluteIriStr);
impl_cmp!(&'a AbsoluteIriString, AbsoluteIriStr, 'a);
impl_cmp!(AbsoluteIriString, &'a AbsoluteIriStr, 'a);
impl_cmp!(&'a AbsoluteIriString, AbsoluteIriString, 'a);
impl_cmp!(AbsoluteIriStr, String);
impl_cmp!(&'a AbsoluteIriStr, String, 'a);
impl_cmp!(AbsoluteIriStr, &'a String, 'a);
impl_cmp!(AbsoluteIriString, str);
impl_cmp!(&'a AbsoluteIriString, str, 'a);
impl_cmp!(AbsoluteIriString, &'a str, 'a);
#[cfg(test)]
mod tests {
use super::*;
fn ensure_ok(s: &str) {
let iri = AbsoluteIri::new(s).unwrap();
let iri_str = AbsoluteIriStr::new(s).unwrap();
let iri_string = AbsoluteIriString::new(s.to_owned()).unwrap();
assert_eq!(s, iri.raw());
assert_eq!(iri.resolved(), &Url::parse(iri.raw()).unwrap());
assert_eq!(iri_string, iri_str);
assert_eq!(iri_string.as_iri_str(), iri_str);
assert_eq!(iri_string.as_iri_str().to_owned(), iri_string);
}
#[test]
fn ok_cases() {
let ok_cases = [
"https://example.com/",
"https://example.com/テスト",
"https://example.com/%e3%83%86ス%E3%83%88",
"https://テスト.日本語/%E3%83%86%E3%82%B9%E3%83%88",
];
ok_cases.into_iter().for_each(|&s| ensure_ok(s));
}
#[cfg(feature = "serde")]
#[test]
fn ensure_serde_traits() {
fn serializable<T: Serialize>(_: T) {}
fn deserializable<'de, T: Deserialize<'de>>(_: T) {}
let s = "https://example.com/";
let iri = AbsoluteIri::new(s).unwrap();
let iri_str = AbsoluteIriStr::new(s).unwrap();
let iri_string = AbsoluteIriString::new(s.to_owned()).unwrap();
serializable(iri.clone());
serializable(iri_str);
serializable(iri_string.clone());
deserializable(iri.clone());
deserializable(iri_str);
deserializable(iri_string.clone());
}
}