path-gen/src/lib.rs

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 {}
}