698 lines
16 KiB
Rust
698 lines
16 KiB
Rust
use std::{
|
|
borrow::Cow,
|
|
fmt::{Debug, Display},
|
|
marker::PhantomData,
|
|
ops::Deref,
|
|
str::{from_utf8, Utf8Error},
|
|
};
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct PathGen<'a> {
|
|
root: Cow<'a, str>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct Prefix<Inner, Segment> {
|
|
inner: Inner,
|
|
segment: PhantomData<Segment>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct Field<Inner, Segment> {
|
|
inner: Inner,
|
|
segment: Segment,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct Node<Inner, Segment> {
|
|
inner: Inner,
|
|
segment: Segment,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct DeconstructError;
|
|
|
|
impl Display for DeconstructError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "Invalid path format")
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for DeconstructError {}
|
|
|
|
pub struct PathParser<Inner> {
|
|
phantom: PhantomData<Inner>,
|
|
}
|
|
|
|
pub trait PathItem<'a> {
|
|
type Error: std::error::Error;
|
|
|
|
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
|
where
|
|
Self: Sized + 'a;
|
|
|
|
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]>;
|
|
}
|
|
|
|
pub trait PathNode<'a>: PathItem<'a> {
|
|
const NAME: &'static [u8];
|
|
}
|
|
|
|
pub trait PathField<'a>: PathItem<'a> {}
|
|
|
|
pub trait IntoOwned {
|
|
type Owned: 'static;
|
|
|
|
fn into_owned(self) -> Self::Owned;
|
|
}
|
|
|
|
impl PathGen<'static> {
|
|
pub const fn new_owned(root: String) -> Self {
|
|
PathGen {
|
|
root: Cow::Owned(root),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> PathGen<'a> {
|
|
pub const fn new(root: &'a str) -> Self {
|
|
PathGen {
|
|
root: Cow::Borrowed(root),
|
|
}
|
|
}
|
|
|
|
pub const fn parser() -> PathParser<Self> {
|
|
PathParser {
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub fn push<N>(self, node: N) -> Node<Self, N>
|
|
where
|
|
N: PathNode<'a>,
|
|
{
|
|
Node {
|
|
inner: self,
|
|
segment: node,
|
|
}
|
|
}
|
|
|
|
pub fn prefix<N>(self) -> Prefix<Self, N>
|
|
where
|
|
N: PathNode<'a>,
|
|
{
|
|
Prefix {
|
|
inner: self,
|
|
segment: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> Node<Inner, Segment> {
|
|
pub fn push<'a, N>(self, node: N) -> Node<Self, N>
|
|
where
|
|
N: PathNode<'a>,
|
|
{
|
|
Node {
|
|
inner: self,
|
|
segment: node,
|
|
}
|
|
}
|
|
|
|
pub fn split(self) -> (Inner, Segment) {
|
|
(self.inner, self.segment)
|
|
}
|
|
|
|
pub const fn parent(&self) -> &Inner {
|
|
&self.inner
|
|
}
|
|
|
|
pub const fn segment(&self) -> &Segment {
|
|
&self.segment
|
|
}
|
|
|
|
pub fn field<'a, F>(self, field: F) -> Field<Self, F>
|
|
where
|
|
F: PathField<'a>,
|
|
{
|
|
Field {
|
|
inner: self,
|
|
segment: field,
|
|
}
|
|
}
|
|
|
|
pub fn prefix<'a, N>(self) -> Prefix<Self, N>
|
|
where
|
|
N: PathNode<'a>,
|
|
{
|
|
Prefix {
|
|
inner: self,
|
|
segment: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> Field<Inner, Segment> {
|
|
pub fn split(self) -> (Inner, Segment) {
|
|
(self.inner, self.segment)
|
|
}
|
|
|
|
pub const fn parent(&self) -> &Inner {
|
|
&self.inner
|
|
}
|
|
|
|
pub const fn segment(&self) -> &Segment {
|
|
&self.segment
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> Prefix<Inner, Segment> {
|
|
pub fn pop(self) -> Inner {
|
|
self.inner
|
|
}
|
|
|
|
pub const fn parent(&self) -> &Inner {
|
|
&self.inner
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> Field<Inner, Segment>
|
|
where
|
|
Self: Construct,
|
|
{
|
|
pub fn to_bytes(&self) -> Vec<u8> {
|
|
Construct::construct(self)
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> Prefix<Inner, Segment>
|
|
where
|
|
Self: Construct,
|
|
{
|
|
pub fn to_bytes(&self) -> Vec<u8> {
|
|
Construct::construct(self)
|
|
}
|
|
}
|
|
|
|
impl<Inner> PathParser<Inner> {
|
|
pub fn push<'a, N>(self) -> PathParser<Node<Inner, N>>
|
|
where
|
|
N: PathNode<'a>,
|
|
{
|
|
PathParser {
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub fn prefix<'a, N>(self) -> PathParser<Prefix<Inner, N>>
|
|
where
|
|
N: PathNode<'a>,
|
|
{
|
|
PathParser {
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub fn field<'a, F>(self) -> PathParser<Field<Inner, F>>
|
|
where
|
|
F: PathField<'a>,
|
|
{
|
|
PathParser {
|
|
phantom: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, F> PathParser<Field<Inner, F>> {
|
|
pub fn parse_str<'a, E>(&self, s: &'a str) -> Result<Field<Inner, F>, E>
|
|
where
|
|
Field<Inner, F>: Deconstruct<'a, E> + 'a,
|
|
{
|
|
self.parse(s.as_bytes())
|
|
}
|
|
|
|
pub fn parse<'a, E>(&self, s: &'a [u8]) -> Result<Field<Inner, F>, E>
|
|
where
|
|
Field<Inner, F>: Deconstruct<'a, E> + 'a,
|
|
{
|
|
Field::<Inner, F>::deconstruct(s)
|
|
}
|
|
}
|
|
|
|
impl<Inner, N> PathParser<Prefix<Inner, N>> {
|
|
pub fn parse_str<'a, E>(&self, s: &'a str) -> Result<Prefix<Inner, N>, E>
|
|
where
|
|
Prefix<Inner, N>: Deconstruct<'a, E> + 'a,
|
|
{
|
|
self.parse(s.as_bytes())
|
|
}
|
|
|
|
pub fn parse<'a, E>(&self, s: &'a [u8]) -> Result<Prefix<Inner, N>, E>
|
|
where
|
|
Prefix<Inner, N>: Deconstruct<'a, E> + 'a,
|
|
{
|
|
Prefix::<Inner, N>::deconstruct(s)
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoOwned for PathGen<'a> {
|
|
type Owned = PathGen<'static>;
|
|
|
|
fn into_owned(self) -> Self::Owned {
|
|
PathGen {
|
|
root: Cow::Owned(self.root.into_owned()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> IntoOwned for Node<Inner, Segment>
|
|
where
|
|
Inner: IntoOwned,
|
|
Segment: IntoOwned,
|
|
{
|
|
type Owned = Node<<Inner as IntoOwned>::Owned, <Segment as IntoOwned>::Owned>;
|
|
|
|
fn into_owned(self) -> Self::Owned {
|
|
Node {
|
|
inner: self.inner.into_owned(),
|
|
segment: self.segment.into_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> IntoOwned for Field<Inner, Segment>
|
|
where
|
|
Inner: IntoOwned,
|
|
Segment: IntoOwned,
|
|
{
|
|
type Owned = Field<<Inner as IntoOwned>::Owned, <Segment as IntoOwned>::Owned>;
|
|
|
|
fn into_owned(self) -> Self::Owned {
|
|
Field {
|
|
inner: self.inner.into_owned(),
|
|
segment: self.segment.into_owned(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> IntoOwned for Prefix<Inner, Segment>
|
|
where
|
|
Inner: IntoOwned,
|
|
Segment: IntoOwned,
|
|
{
|
|
type Owned = Prefix<<Inner as IntoOwned>::Owned, <Segment as IntoOwned>::Owned>;
|
|
|
|
fn into_owned(self) -> Self::Owned {
|
|
Prefix {
|
|
inner: self.inner.into_owned(),
|
|
segment: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> Deref for Node<Inner, Segment> {
|
|
type Target = Segment;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
self.segment()
|
|
}
|
|
}
|
|
|
|
impl<Inner, Segment> Deref for Field<Inner, Segment> {
|
|
type Target = Segment;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
self.segment()
|
|
}
|
|
}
|
|
|
|
mod hidden {
|
|
pub trait Construct {
|
|
fn construct(&self) -> Vec<u8>;
|
|
}
|
|
|
|
pub trait Deconstruct<'a, E> {
|
|
fn deconstruct(s: &'a [u8]) -> Result<Self, E>
|
|
where
|
|
Self: Sized + 'a;
|
|
}
|
|
}
|
|
use hidden::{Construct, Deconstruct};
|
|
|
|
impl<'a> Construct for PathGen<'a> {
|
|
fn construct(&self) -> Vec<u8> {
|
|
self.root.as_bytes().to_vec()
|
|
}
|
|
}
|
|
|
|
impl<'a, E> Deconstruct<'a, E> for PathGen<'a>
|
|
where
|
|
E: From<Utf8Error>,
|
|
{
|
|
fn deconstruct(s: &'a [u8]) -> Result<Self, E> {
|
|
Ok(PathGen {
|
|
root: Cow::Borrowed(from_utf8(s)?),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a, E, Inner, N> Deconstruct<'a, E> for Node<Inner, N>
|
|
where
|
|
Inner: Deconstruct<'a, E>,
|
|
N: PathNode<'a>,
|
|
E: From<<N as PathItem<'a>>::Error>,
|
|
E: From<DeconstructError>,
|
|
{
|
|
fn deconstruct(s: &'a [u8]) -> Result<Self, E>
|
|
where
|
|
Self: Sized + 'a,
|
|
{
|
|
if let Some((rest, value)) = rsplit_once_escaped(s) {
|
|
if let Some((rest, name)) = rsplit_once_escaped(rest) {
|
|
if unescape(name) == N::NAME {
|
|
let inner = Inner::deconstruct(rest)?;
|
|
let unescaped = unescape(value);
|
|
let segment = N::parse(unescaped)?;
|
|
return Ok(Node { inner, segment });
|
|
}
|
|
}
|
|
}
|
|
|
|
Err(DeconstructError.into())
|
|
}
|
|
}
|
|
|
|
impl<'a, E, Inner, F> Deconstruct<'a, E> for Field<Inner, F>
|
|
where
|
|
Inner: Deconstruct<'a, E>,
|
|
F: PathField<'a>,
|
|
E: From<<F as PathItem<'a>>::Error>,
|
|
E: From<DeconstructError>,
|
|
{
|
|
fn deconstruct(s: &'a [u8]) -> Result<Self, E>
|
|
where
|
|
Self: Sized + 'a,
|
|
{
|
|
if let Some((rest, value)) = rsplit_once_escaped(s) {
|
|
let inner = Inner::deconstruct(rest)?;
|
|
let segment = F::parse(unescape(value))?;
|
|
return Ok(Field { inner, segment });
|
|
} else {
|
|
Err(DeconstructError.into())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, E, Inner, N> Deconstruct<'a, E> for Prefix<Inner, N>
|
|
where
|
|
Inner: Deconstruct<'a, E>,
|
|
N: PathNode<'a>,
|
|
E: From<<N as PathItem<'a>>::Error>,
|
|
E: From<DeconstructError>,
|
|
{
|
|
fn deconstruct(s: &'a [u8]) -> Result<Self, E>
|
|
where
|
|
Self: Sized + 'a,
|
|
{
|
|
if let Some((rest, name)) = rsplit_once_escaped(s) {
|
|
if unescape(name) == N::NAME {
|
|
let inner = Inner::deconstruct(rest)?;
|
|
return Ok(Prefix {
|
|
inner,
|
|
segment: PhantomData,
|
|
});
|
|
}
|
|
}
|
|
|
|
Err(DeconstructError.into())
|
|
}
|
|
}
|
|
|
|
impl<'a, Inner, N> Construct for Node<Inner, N>
|
|
where
|
|
Inner: Construct,
|
|
N: PathNode<'a>,
|
|
{
|
|
fn construct(&self) -> Vec<u8> {
|
|
let mut vec = self.inner.construct();
|
|
vec.extend_from_slice(b".");
|
|
vec.extend_from_slice(&escape(N::NAME.into()));
|
|
vec.extend_from_slice(b".");
|
|
vec.extend_from_slice(&escape(&self.segment.to_bytes()));
|
|
vec
|
|
}
|
|
}
|
|
|
|
impl<'a, Inner, F> Construct for Field<Inner, F>
|
|
where
|
|
Inner: Construct,
|
|
F: PathField<'a>,
|
|
{
|
|
fn construct(&self) -> Vec<u8> {
|
|
let mut vec = self.inner.construct();
|
|
vec.extend_from_slice(b".");
|
|
vec.extend_from_slice(&escape(&self.segment.to_bytes()));
|
|
vec
|
|
}
|
|
}
|
|
|
|
impl<'a, Inner, N> Construct for Prefix<Inner, N>
|
|
where
|
|
Inner: Construct,
|
|
N: PathNode<'a>,
|
|
{
|
|
fn construct(&self) -> Vec<u8> {
|
|
let mut vec = self.inner.construct();
|
|
vec.extend_from_slice(b".");
|
|
vec.extend_from_slice(&escape(N::NAME.into()));
|
|
vec
|
|
}
|
|
}
|
|
|
|
const DELIMITER: u8 = b'.';
|
|
const ESCAPE_TOKEN: u8 = b'\\';
|
|
|
|
fn escape(s: &[u8]) -> Cow<'_, [u8]> {
|
|
flee::escape(s, DELIMITER, ESCAPE_TOKEN)
|
|
}
|
|
|
|
fn unescape(s: &[u8]) -> Cow<'_, [u8]> {
|
|
flee::unescape(s, DELIMITER, ESCAPE_TOKEN)
|
|
}
|
|
|
|
fn rsplit_once_escaped(s: &[u8]) -> Option<(&[u8], &[u8])> {
|
|
flee::rsplit_once_escaped(s, DELIMITER, ESCAPE_TOKEN)
|
|
}
|
|
|
|
#[cfg(feature = "test")]
|
|
pub mod test {
|
|
use super::{PathField, PathItem, PathNode};
|
|
use std::{borrow::Cow, fmt::Debug};
|
|
|
|
pub fn test_pathnode<'a, 'b, N, N2, E>(path_node: N)
|
|
where
|
|
N: PathNode<'a, Error = E> + Debug,
|
|
N2: PathNode<'b, Error = E> + Debug + PartialEq<N> + 'b,
|
|
E: Debug,
|
|
{
|
|
test_roundtrip::<'a, 'b, N, N2, E>(path_node);
|
|
}
|
|
|
|
pub fn test_field<'a, 'b, F, F2, E>(path_field: F)
|
|
where
|
|
F: PathField<'a, Error = E> + Debug,
|
|
F2: PathField<'b, Error = E> + Debug + PartialEq<F> + 'b,
|
|
E: Debug,
|
|
{
|
|
test_roundtrip::<'a, 'b, F, F2, E>(path_field)
|
|
}
|
|
|
|
fn test_roundtrip<'a, 'b, T, T2, E>(item: T)
|
|
where
|
|
T: PathItem<'a, Error = E> + Debug,
|
|
T2: PathItem<'b, Error = E> + Debug + PartialEq<T> + 'b,
|
|
E: Debug,
|
|
{
|
|
let vec = item.to_bytes().to_vec();
|
|
let round_trip = T2::parse(Cow::Owned(vec.clone())).unwrap();
|
|
let plus_one = round_trip.to_bytes().to_vec();
|
|
|
|
assert_eq!(round_trip, item);
|
|
assert_eq!(vec, plus_one);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::{
|
|
borrow::Cow,
|
|
fmt::Display,
|
|
num::ParseIntError,
|
|
str::{from_utf8, Utf8Error},
|
|
};
|
|
|
|
use super::{PathField, PathGen, PathItem, PathNode};
|
|
|
|
const ROOT: PathGen<'static> = PathGen::new("test-root");
|
|
|
|
#[test]
|
|
fn construct_field() {
|
|
let s = ROOT
|
|
.push(Deck("deck-id".into()))
|
|
.push(Key(5))
|
|
.field(CommandField)
|
|
.to_bytes();
|
|
|
|
assert_eq!(&s, b"test-root.deck.deck-id.key.5.command")
|
|
}
|
|
|
|
#[test]
|
|
fn construct_prefix() {
|
|
let s = ROOT.push(Deck("deck-id".into())).prefix::<Key>().to_bytes();
|
|
|
|
assert_eq!(&s, b"test-root.deck.deck-id.key")
|
|
}
|
|
|
|
#[test]
|
|
fn construct_escaped_prefix() {
|
|
let s = ROOT.push(Deck("deck.id".into())).prefix::<Key>().to_bytes();
|
|
|
|
assert_eq!(&s, b"test-root.deck.deck\\.id.key")
|
|
}
|
|
|
|
#[test]
|
|
fn deconstruct_field() {
|
|
let parser = PathGen::parser()
|
|
.push::<Deck>()
|
|
.push::<Key>()
|
|
.field::<CommandField>();
|
|
|
|
let s = b"test-root.deck.deck-id.key.5.command";
|
|
|
|
let path = parser.parse::<Box<dyn std::error::Error>>(s).unwrap();
|
|
|
|
assert_eq!(
|
|
path,
|
|
ROOT.push(Deck("deck-id".into()))
|
|
.push(Key(5))
|
|
.field(CommandField)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn deconstruct_prefix() {
|
|
let parser = PathGen::parser().push::<Deck>().prefix::<Key>();
|
|
|
|
let s = b"test-root.deck.deck-id.key";
|
|
|
|
let path = parser.parse::<Box<dyn std::error::Error>>(s).unwrap();
|
|
|
|
assert_eq!(path, ROOT.push(Deck("deck-id".into())).prefix::<Key>());
|
|
}
|
|
|
|
#[test]
|
|
fn deconstruct_escaped_prefix() {
|
|
let parser = PathGen::parser().push::<Deck>().prefix::<Key>();
|
|
|
|
let s = b"test-root.deck.deck\\.id.key";
|
|
|
|
let path = parser.parse::<Box<dyn std::error::Error>>(s).unwrap();
|
|
|
|
assert_eq!(path, ROOT.push(Deck("deck.id".into())).prefix::<Key>());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parts() {
|
|
let string = String::from("hello");
|
|
let brw: &str = &string;
|
|
|
|
super::test::test_pathnode::<_, Deck<'_>, _>(Deck(Cow::Borrowed(brw)));
|
|
super::test::test_pathnode::<_, Key, _>(Key(5));
|
|
super::test::test_field::<_, CommandField, _>(CommandField);
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
struct Deck<'a>(Cow<'a, str>);
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
struct Key(u8);
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
struct CommandField;
|
|
#[derive(Debug)]
|
|
struct CommandFieldError;
|
|
|
|
impl Display for CommandFieldError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "string did not match 'command'")
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for CommandFieldError {}
|
|
|
|
impl<'a> PathItem<'a> for Deck<'a> {
|
|
type Error = Utf8Error;
|
|
|
|
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
|
where
|
|
Self: Sized + 'a,
|
|
{
|
|
let cow = match bytes {
|
|
Cow::Owned(bytes) => {
|
|
from_utf8(&bytes)?;
|
|
Cow::Owned(String::from_utf8(bytes).unwrap())
|
|
}
|
|
Cow::Borrowed(bytes) => Cow::Borrowed(from_utf8(bytes)?),
|
|
};
|
|
|
|
Ok(Deck(cow))
|
|
}
|
|
|
|
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
|
Cow::Borrowed(self.0.as_bytes())
|
|
}
|
|
}
|
|
|
|
impl<'a> PathItem<'a> for Key {
|
|
type Error = ParseIntError;
|
|
|
|
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
|
where
|
|
Self: Sized + 'a,
|
|
{
|
|
Ok(Key(String::from_utf8_lossy(&bytes).parse()?))
|
|
}
|
|
|
|
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
|
Cow::Owned(self.0.to_string().into_bytes())
|
|
}
|
|
}
|
|
|
|
impl<'a> PathItem<'a> for CommandField {
|
|
type Error = CommandFieldError;
|
|
|
|
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
|
where
|
|
Self: Sized + 'a,
|
|
{
|
|
if bytes.as_ref() == b"command" {
|
|
return Ok(CommandField);
|
|
}
|
|
|
|
Err(CommandFieldError)
|
|
}
|
|
|
|
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
|
Cow::Borrowed(b"command")
|
|
}
|
|
}
|
|
|
|
impl<'a> PathNode<'a> for Deck<'a> {
|
|
const NAME: &'static [u8] = b"deck";
|
|
}
|
|
|
|
impl<'a> PathNode<'a> for Key {
|
|
const NAME: &'static [u8] = b"key";
|
|
}
|
|
|
|
impl<'a> PathField<'a> for CommandField {}
|
|
}
|