Skip to content

Instantly share code, notes, and snippets.

@lo48576
Last active August 28, 2017 10:51
Show Gist options
  • Save lo48576/c0285251edc851eeea367f3a4c114b00 to your computer and use it in GitHub Desktop.
Save lo48576/c0285251edc851eeea367f3a4c114b00 to your computer and use it in GitHub Desktop.
IRI in Rust
//! IRI types.
// This implementation is based on `str`, `String`, `std::str` and `std::path`
// on rust-1.19.0 .
// See:
//
// * https://github.com/rust-lang/rust/blob/1.19.0/src/libcore/str/mod.rs
// * https://github.com/rust-lang/rust/blob/1.19.0/src/libcollections/string.rs
// * https://github.com/rust-lang/rust/blob/1.19.0/src/libstd/path.rs
use std::borrow::{Borrow, ToOwned, Cow};
use std::cmp;
use std::ops::Deref;
use std::error;
use std::fmt;
use url; // extern crate.
/// An IRI slice.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Iri {
inner: str,
}
impl Iri {
/// Converts a string slice to an IRI slice without checking that the string contains valid
/// IRI.
#[inline]
pub unsafe fn from_str_unchecked(s: &str) -> &Self {
::std::mem::transmute(s)
}
/// Converts a string slice to an IRI slice.
pub fn from_str(s: &str) -> Result<(&Self, url::Url), ParseError> {
let url = run_iri_validation(s)?;
let iri = unsafe { Self::from_str_unchecked(s) };
Ok((iri, url))
}
/// Converts an `Iri` to an owned `IriBuf`.
#[inline]
pub fn to_iri_buf(&self) -> IriBuf {
IriBuf { inner: self.inner.to_owned() }
}
/// Converts an `Iri` to an `url::Url`.
pub fn to_url(&self) -> url::Url {
url::Url::parse(self).expect(
"Failed to convert an `Iri` to an `Url::url` (should never happen)",
)
}
}
impl AsRef<str> for Iri {
fn as_ref(&self) -> &str {
self
}
}
impl AsRef<Iri> for Iri {
fn as_ref(&self) -> &Iri {
self
}
}
impl Deref for Iri {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ToOwned for Iri {
type Owned = IriBuf;
fn to_owned(&self) -> Self::Owned {
self.to_iri_buf()
}
}
impl<'a> From<&'a Iri> for url::Url {
fn from(v: &Iri) -> Self {
v.to_url()
}
}
/// An owned IRI.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IriBuf {
inner: String,
}
impl IriBuf {
/// Converts a string to an owned IRI without checking that the string contains valid IRI.
#[inline]
pub unsafe fn from_string_unchecked(s: String) -> Self {
IriBuf { inner: s }
}
/// Converts a string to an owned IRI.
pub fn from_string(s: String) -> Result<(Self, url::Url), ParseError> {
let url = run_iri_validation(&s)?;
let iri = Self { inner: s };
Ok((iri, url))
}
/// Coerces to an `Iri` slice.
#[inline]
pub fn as_iri(&self) -> &Iri {
unsafe { Iri::from_str_unchecked(self.inner.as_str()) }
}
/// Converts an `IriBuf` to an `url::Url`.
pub fn to_url(&self) -> url::Url {
url::Url::parse(self).expect(
"Failed to convert an `IriBuf` to an `Url::url` (should never happen)",
)
}
}
impl AsRef<str> for IriBuf {
fn as_ref(&self) -> &str {
self
}
}
impl AsRef<Iri> for IriBuf {
fn as_ref(&self) -> &Iri {
self
}
}
impl Borrow<Iri> for IriBuf {
fn borrow(&self) -> &Iri {
self.as_iri()
}
}
impl Deref for IriBuf {
type Target = Iri;
fn deref(&self) -> &Self::Target {
self.as_iri()
}
}
impl ::std::str::FromStr for IriBuf {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Iri::from_str(s).map(|(iri, _)| iri).map(ToOwned::to_owned)
}
}
impl From<IriBuf> for url::Url {
fn from(v: IriBuf) -> Self {
v.to_url()
}
}
// Based on https://github.com/rust-lang/rust/blob/1.19.0/src/libstd/path.rs#L2424-L2460
macro_rules! impl_cmp {
($lhs:ty, $rhs:ty) => {
impl<'a, 'b> PartialEq<$rhs> for $lhs {
#[inline]
fn eq(&self, other: &$rhs) -> bool { <Iri as PartialEq>::eq(self, other) }
#[inline]
fn ne(&self, other: &$rhs) -> bool { <Iri as PartialEq>::ne(self, other) }
}
impl<'a, 'b> PartialEq<$lhs> for $rhs {
#[inline]
fn eq(&self, other: &$lhs) -> bool { <Iri as PartialEq>::eq(self, other) }
#[inline]
fn ne(&self, other: &$lhs) -> bool { <Iri as PartialEq>::ne(self, other) }
}
impl<'a, 'b> PartialOrd<$rhs> for $lhs {
#[inline]
fn partial_cmp(&self, other: &$rhs) -> Option<cmp::Ordering> {
<Iri as PartialOrd>::partial_cmp(self, other)
}
}
impl<'a, 'b> PartialOrd<$lhs> for $rhs {
#[inline]
fn partial_cmp(&self, other: &$lhs) -> Option<cmp::Ordering> {
<Iri as PartialOrd>::partial_cmp(self, other)
}
}
};
}
impl_cmp!(IriBuf, Iri);
impl_cmp!(IriBuf, &'a Iri);
impl_cmp!(Cow<'a, Iri>, Iri);
impl_cmp!(Cow<'a, Iri>, &'b Iri);
impl_cmp!(Cow<'a, Iri>, IriBuf);
/// An IRI parse error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseError {
/// Parse error from the `url` crate.
Url(url::ParseError),
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ParseError::Url(ref e) = *self;
write!(f, "Provided string was invalid as IRI: {}", e)
}
}
impl error::Error for ParseError {
fn description(&self) -> &str {
let ParseError::Url(ref e) = *self;
error::Error::description(e)
}
}
impl From<url::ParseError> for ParseError {
fn from(v: url::ParseError) -> Self {
ParseError::Url(v)
}
}
/// Checks whether the given string is valid IRI.
fn run_iri_validation(s: &str) -> Result<url::Url, ParseError> {
Ok(url::Url::parse(s)?)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment