use crate::error::{Error as IriError, ErrorKind, Result as IriResult};
use crate::{Authority, Fragment, Normalize, Path, PercentEncoding, Port, Query, Scheme};
use regex::Regex;
use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use uuid::Uuid;
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IRI {
scheme: Option<Scheme>,
authority: Option<Authority>,
path: Path,
query: Option<Query>,
fragment: Option<Fragment>,
}
#[allow(clippy::upper_case_acronyms)]
pub type IRIRef = Arc<IRI>;
impl Default for IRI {
fn default() -> Self {
Self::new(&Path::default())
}
}
impl Display for IRI {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}",
match &self.scheme {
None => String::new(),
Some(scheme) => scheme.to_string(),
},
&self.scheme_specific_part()
)
}
}
impl FromStr for IRI {
type Err = IriError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_iri(s)
}
}
impl From<Path> for IRI {
fn from(path: Path) -> Self {
Self::from(&path)
}
}
impl From<&Path> for IRI {
fn from(path: &Path) -> Self {
Self::new(path)
}
}
#[cfg(feature = "path_iri")]
impl TryFrom<PathBuf> for IRI {
type Error = IriError;
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
Self::try_from(&path)
}
}
#[cfg(feature = "path_iri")]
impl TryFrom<&PathBuf> for IRI {
type Error = IriError;
fn try_from(path: &PathBuf) -> Result<Self, Self::Error> {
Self::new_file(path)
}
}
#[cfg(feature = "uuid_iri")]
impl TryFrom<Uuid> for IRI {
type Error = IriError;
fn try_from(path: Uuid) -> Result<Self, Self::Error> {
Self::try_from(&path)
}
}
#[cfg(feature = "uuid_iri")]
impl TryFrom<&Uuid> for IRI {
type Error = IriError;
fn try_from(path: &Uuid) -> Result<Self, Self::Error> {
Self::new_name("uuid", &path.to_hyphenated().to_string())
}
}
impl Normalize for IRI {
fn normalize(self) -> IriResult<Self> {
let scheme = match &self.scheme {
None => None,
Some(scheme) => Some(scheme.clone().normalize()?),
};
let authority = match &self.authority {
None => None,
Some(authority) => {
let mut authority = authority.clone().normalize()?;
if self.has_scheme() && !authority.has_port() {
let scheme = self.scheme().as_ref().unwrap();
let scheme = scheme.clone().normalize().unwrap();
if let Some(port) = Port::default_for(&scheme) {
authority.set_port(port);
}
}
Some(authority)
}
};
let mut path = self.path.normalize()?;
if let Some(scheme) = &scheme {
if vec!["file", "ftp", "http", "https", "tftp"].contains(&scheme.value().as_str())
&& path.is_empty()
{
path = Path::root();
}
}
let query = match self.query {
None => None,
Some(query) => Some(query.normalize()?),
};
let fragment = match self.fragment {
None => None,
Some(fragment) => Some(fragment.normalize()?),
};
Ok(Self {
scheme,
authority,
path,
query,
fragment,
})
}
}
impl PercentEncoding for IRI {
fn encode(&self, for_uri: bool) -> Self
where
Self: Sized,
{
Self {
scheme: self.scheme.clone(),
authority: self.authority.as_ref().map(|a| a.encode(for_uri)),
path: self.path.encode(for_uri),
query: self.query.as_ref().map(|q| q.encode(for_uri)),
fragment: self.fragment.as_ref().map(|f| f.encode(for_uri)),
}
}
}
impl IRI {
pub fn new(path: &Path) -> Self {
Self {
scheme: None,
authority: None,
path: path.clone(),
query: None,
fragment: None,
}
}
#[cfg(feature = "path_iri")]
pub fn new_file(path: &std::path::Path) -> IriResult<Self> {
Ok(Self {
scheme: Some(Scheme::file()),
authority: None,
path: Path::from_str(&path.to_string_lossy().to_string())?,
query: None,
fragment: None,
})
}
pub fn new_name(
namespace_identifier: &str,
namespace_specific_string: &str,
) -> IriResult<Self> {
Ok(Self {
scheme: Some(Scheme::urn()),
authority: None,
path: Path::from_str(&format!(
"{}:{}",
namespace_identifier, namespace_specific_string
))?,
query: None,
fragment: None,
})
}
#[cfg(feature = "genid")]
pub fn new_genid(base: &IRI) -> IriResult<Self> {
let new_uuid = Uuid::new_v4();
let new_uuid = new_uuid
.to_simple()
.encode_lower(&mut Uuid::encode_buffer())
.to_string();
let mut path = Path::well_known();
path.push("genid")?;
path.push(&new_uuid)?;
let mut iri: IRI = base.clone();
iri.set_path(path);
Ok(iri)
}
pub fn with_new_path(&self, path: Path) -> Self {
Self {
path,
..self.clone()
}
}
pub fn without_path(&self) -> Self {
Self {
path: Path::default(),
..self.clone()
}
}
pub fn with_new_query(&self, query: Query) -> Self {
Self {
query: Some(query),
..self.clone()
}
}
pub fn without_query(&self) -> Self {
Self {
query: None,
..self.clone()
}
}
pub fn with_new_fragment(&self, fragment: Fragment) -> Self {
Self {
fragment: Some(fragment),
..self.clone()
}
}
pub fn without_fragment(&self) -> Self {
Self {
fragment: None,
..self.clone()
}
}
pub fn resolve(&self, relative: &IRI) -> IriResult<Self> {
if relative.is_absolute() || self.is_opaque() {
Ok(relative.clone())
} else if !relative.has_scheme()
&& !relative.has_authority()
&& relative.path().is_empty()
&& !relative.has_query()
&& relative.has_fragment()
{
Ok(self.with_new_fragment(relative.fragment().as_ref().unwrap().clone()))
} else {
unimplemented!()
}
}
pub fn relativize(&self, _other: &IRIRef) -> IriResult<Self> {
unimplemented!()
}
pub fn is_absolute(&self) -> bool {
self.has_scheme() && !self.has_fragment()
}
pub fn is_relative_reference(&self) -> bool {
!self.has_scheme()
}
pub fn is_opaque(&self) -> bool {
let ssp = self.scheme_specific_part();
self.is_absolute() && !ssp.is_empty() && !ssp.starts_with('/')
}
pub fn is_well_known(&self) -> bool {
self.path().is_well_known()
}
pub fn has_scheme(&self) -> bool {
self.scheme.is_some()
}
pub fn scheme(&self) -> &Option<Scheme> {
&self.scheme
}
pub fn scheme_specific_part(&self) -> String {
format!(
"{}{}{}{}",
match &self.authority {
None => String::new(),
Some(authority) => authority.to_string(),
},
&self.path.to_string(),
match &self.query {
None => String::new(),
Some(query) => query.to_string(),
},
match &self.fragment {
None => String::new(),
Some(fragment) => fragment.to_string(),
},
)
}
pub fn has_authority(&self) -> bool {
self.authority.is_some()
}
pub fn authority(&self) -> &Option<Authority> {
&self.authority
}
pub fn has_path(&self) -> bool {
!self.path.is_empty()
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn has_query(&self) -> bool {
self.query.is_some()
}
pub fn query(&self) -> &Option<Query> {
&self.query
}
pub fn has_fragment(&self) -> bool {
self.fragment.is_some()
}
pub fn fragment(&self) -> &Option<Fragment> {
&self.fragment
}
pub fn set_scheme(&mut self, scheme: Option<Scheme>) {
self.scheme = scheme;
}
pub fn set_authority(&mut self, authority: Option<Authority>) {
self.authority = authority;
}
pub fn set_path(&mut self, path: Path) {
self.path = path;
}
pub fn set_query(&mut self, query: Option<Query>) {
self.query = query;
}
pub fn set_fragment(&mut self, fragment: Option<Fragment>) {
self.fragment = fragment;
}
}
const GRP_SCHEME: usize = 2;
const GRP_AUTHORITY: usize = 4;
const GRP_PATH: usize = 5;
const GRP_QUERY: usize = 7;
const GRP_FRAGMENT: usize = 9;
fn parse_iri(s: &str) -> IriResult<IRI> {
let regex = Regex::new(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?").unwrap();
match regex.captures(s) {
Some(captures) => Ok(IRI {
scheme: match captures.get(GRP_SCHEME) {
None => None,
Some(grp) => Some(Scheme::from_str(grp.as_str())?),
},
authority: match captures.get(GRP_AUTHORITY) {
None => None,
Some(grp) => Some(Authority::from_str(grp.as_str())?),
},
path: match captures.get(GRP_PATH) {
None => Path::default(),
Some(grp) => Path::from_str(grp.as_str())?,
},
query: match captures.get(GRP_QUERY) {
None => None,
Some(grp) => Some(Query::from_str(grp.as_str())?),
},
fragment: match captures.get(GRP_FRAGMENT) {
None => None,
Some(grp) => Some(Fragment::from_str(grp.as_str())?),
},
}),
None => Err(ErrorKind::Syntax(s.to_string()).into()),
}
}