#![warn(missing_docs, clippy::all)]
use lazy_static::lazy_static;
use regex::Regex;
use serde::de::{self, Deserialize, Deserializer, Unexpected};
use serde::ser::{Serialize, Serializer};
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::error::Error;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
#[cfg(test)]
mod test;
const RID_CLASS: &str = "ri";
const SEPARATOR: &str = ".";
lazy_static! {
static ref PARSE_REGEX: Regex = Regex::new(
r"(?x)
^
ri
\.
([a-z][a-z0-9\-]*) #service
\.
((?:[a-z0-9][a-z0-9\-]*)?) #instance
\.
([a-z][a-z0-9\-]*) #type
\.
([a-zA-Z0-9_\-\.]+) #locator
$
",
)
.unwrap();
}
#[derive(Clone)]
pub struct ResourceIdentifier {
rid: String,
service_end: usize,
instance_end: usize,
type_end: usize,
}
impl ResourceIdentifier {
#[inline]
pub fn new(s: &str) -> Result<ResourceIdentifier, ParseError> {
s.parse()
}
pub fn from_components(
service: &str,
instance: &str,
type_: &str,
locator: &str,
) -> Result<ResourceIdentifier, ParseError> {
if service.contains('.') || instance.contains('.') || type_.contains('.') {
return Err(ParseError(()));
}
format!("ri.{service}.{instance}.{type_}.{locator}").parse()
}
#[inline]
pub fn service(&self) -> &str {
let start = RID_CLASS.len() + SEPARATOR.len();
&self.rid[start..self.service_end]
}
#[inline]
pub fn instance(&self) -> &str {
let start = self.service_end + SEPARATOR.len();
&self.rid[start..self.instance_end]
}
#[inline]
pub fn type_(&self) -> &str {
let start = self.instance_end + SEPARATOR.len();
&self.rid[start..self.type_end]
}
#[inline]
pub fn locator(&self) -> &str {
let start = self.type_end + SEPARATOR.len();
&self.rid[start..]
}
#[inline]
pub fn as_str(&self) -> &str {
&self.rid
}
#[inline]
pub fn into_string(self) -> String {
self.rid
}
}
impl FromStr for ResourceIdentifier {
type Err = ParseError;
fn from_str(s: &str) -> Result<ResourceIdentifier, ParseError> {
let captures = match PARSE_REGEX.captures(s) {
Some(captures) => captures,
None => return Err(ParseError(())),
};
Ok(ResourceIdentifier {
rid: s.to_string(),
service_end: captures.get(1).unwrap().end(),
instance_end: captures.get(2).unwrap().end(),
type_end: captures.get(3).unwrap().end(),
})
}
}
impl Serialize for ResourceIdentifier {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.rid.serialize(s)
}
}
impl<'de> Deserialize<'de> for ResourceIdentifier {
fn deserialize<D>(d: D) -> Result<ResourceIdentifier, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(d)?;
ResourceIdentifier::new(&s)
.map_err(|_| de::Error::invalid_value(Unexpected::Str(&s), &"a resource identifier"))
}
}
impl AsRef<str> for ResourceIdentifier {
#[inline]
fn as_ref(&self) -> &str {
&self.rid
}
}
impl Borrow<str> for ResourceIdentifier {
#[inline]
fn borrow(&self) -> &str {
&self.rid
}
}
impl fmt::Debug for ResourceIdentifier {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.rid, fmt)
}
}
impl fmt::Display for ResourceIdentifier {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.rid, fmt)
}
}
impl PartialEq for ResourceIdentifier {
#[inline]
fn eq(&self, other: &ResourceIdentifier) -> bool {
self.rid == other.rid
}
}
impl Eq for ResourceIdentifier {}
impl PartialOrd for ResourceIdentifier {
#[inline]
fn partial_cmp(&self, other: &ResourceIdentifier) -> Option<Ordering> {
Some(self.cmp(other))
}
#[inline]
fn gt(&self, other: &ResourceIdentifier) -> bool {
self.rid > other.rid
}
#[inline]
fn ge(&self, other: &ResourceIdentifier) -> bool {
self.rid >= other.rid
}
#[inline]
fn lt(&self, other: &ResourceIdentifier) -> bool {
self.rid < other.rid
}
#[inline]
fn le(&self, other: &ResourceIdentifier) -> bool {
self.rid <= other.rid
}
}
impl Ord for ResourceIdentifier {
#[inline]
fn cmp(&self, other: &ResourceIdentifier) -> Ordering {
self.rid.cmp(&other.rid)
}
}
impl Hash for ResourceIdentifier {
#[inline]
fn hash<H>(&self, hasher: &mut H)
where
H: Hasher,
{
self.rid.hash(hasher)
}
}
#[derive(Debug)]
pub struct ParseError(());
impl fmt::Display for ParseError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("invalid resource identifier")
}
}
impl Error for ParseError {}