Deal in bytes

This commit is contained in:
Aode (lion) 2021-10-06 13:54:25 -05:00
parent cd0af4d693
commit 3dfc69dd0c

View file

@ -1,4 +1,9 @@
use std::{borrow::Cow, fmt::Display, marker::PhantomData, str::FromStr};
use std::{
borrow::{Borrow, Cow},
fmt::{Debug, Display},
marker::PhantomData,
str::{from_utf8, Utf8Error},
};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PathGen<'a> {
@ -26,7 +31,7 @@ pub struct Node<Inner, Segment> {
#[derive(Debug)]
pub struct DeconstructError;
impl std::fmt::Display for DeconstructError {
impl Display for DeconstructError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid path format")
}
@ -38,11 +43,27 @@ pub struct PathParser<Inner> {
phantom: PhantomData<Inner>,
}
pub trait PathNode: Display + FromStr {
const NAME: &'static str;
pub trait PathItem {
type Error: std::error::Error;
fn parse<'a>(bytes: &'a [u8]) -> Result<Self, Self::Error>
where
Self: Sized + 'a;
fn to_bytes(&self) -> Vec<u8>;
}
pub trait PathField: Display + FromStr {}
pub trait PathNode: PathItem {
const NAME: &'static [u8];
}
pub trait PathField: PathItem {}
pub trait IntoOwned {
type Owned: 'static;
fn into_owned(self) -> Self::Owned;
}
impl PathGen<'static> {
pub fn new_owned(root: String) -> Self {
@ -138,7 +159,7 @@ impl<Inner, Segment> Field<Inner, Segment>
where
Self: Construct,
{
pub fn construct(&self) -> String {
pub fn to_bytes(&self) -> Vec<u8> {
Construct::construct(self)
}
}
@ -147,29 +168,11 @@ impl<Inner, Segment> Prefix<Inner, Segment>
where
Self: Construct,
{
pub fn construct(&self) -> String {
pub fn to_bytes(&self) -> Vec<u8> {
Construct::construct(self)
}
}
impl<Inner, Segment> Display for Field<Inner, Segment>
where
Self: Construct,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.construct())
}
}
impl<Inner, Segment> Display for Prefix<Inner, Segment>
where
Self: Construct,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.construct())
}
}
impl<Inner> PathParser<Inner> {
pub fn push<N>(self) -> PathParser<Node<Inner, N>>
where
@ -200,7 +203,14 @@ impl<Inner> PathParser<Inner> {
}
impl<Inner, F> PathParser<Field<Inner, F>> {
pub fn parse<'a, E>(&self, s: &'a str) -> Result<Field<Inner, F>, E>
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,
{
@ -209,7 +219,14 @@ impl<Inner, F> PathParser<Field<Inner, F>> {
}
impl<Inner, N> PathParser<Prefix<Inner, N>> {
pub fn parse<'a, E>(&self, s: &'a str) -> Result<Prefix<Inner, N>, E>
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,
{
@ -217,13 +234,68 @@ impl<Inner, N> PathParser<Prefix<Inner, N>> {
}
}
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,
}
}
}
mod hidden {
pub trait Construct {
fn construct(&self) -> String;
fn construct(&self) -> Vec<u8>;
}
pub trait Deconstruct<'a, E> {
fn deconstruct(s: &'a str) -> Result<Self, E>
fn deconstruct(s: &'a [u8]) -> Result<Self, E>
where
Self: Sized + 'a;
}
@ -231,15 +303,18 @@ mod hidden {
use hidden::{Construct, Deconstruct};
impl<'a> Construct for PathGen<'a> {
fn construct(&self) -> String {
self.root.to_string()
fn construct(&self) -> Vec<u8> {
self.root.as_bytes().to_vec()
}
}
impl<'a, E> Deconstruct<'a, E> for PathGen<'a> {
fn deconstruct(s: &'a str) -> Result<Self, E> {
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(s),
root: Cow::Borrowed(from_utf8(s)?),
})
}
}
@ -248,10 +323,10 @@ impl<'a, E, Inner, N> Deconstruct<'a, E> for Node<Inner, N>
where
Inner: Deconstruct<'a, E>,
N: PathNode,
E: From<<N as FromStr>::Err>,
E: From<<N as PathItem>::Error>,
E: From<DeconstructError>,
{
fn deconstruct(s: &'a str) -> Result<Self, E>
fn deconstruct(s: &'a [u8]) -> Result<Self, E>
where
Self: Sized + 'a,
{
@ -259,7 +334,7 @@ where
if let Some((rest, name)) = rsplit_once_escaped(rest) {
if unescape(name) == N::NAME {
let inner = Inner::deconstruct(rest)?;
let segment = N::from_str(&unescape(value))?;
let segment = N::parse(&unescape(value))?;
return Ok(Node { inner, segment });
}
}
@ -273,16 +348,16 @@ impl<'a, E, Inner, F> Deconstruct<'a, E> for Field<Inner, F>
where
Inner: Deconstruct<'a, E>,
F: PathField,
E: From<<F as FromStr>::Err>,
E: From<<F as PathItem>::Error>,
E: From<DeconstructError>,
{
fn deconstruct(s: &'a str) -> Result<Self, E>
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::from_str(&unescape(value))?;
let segment = F::parse(&unescape(value))?;
return Ok(Field { inner, segment });
} else {
Err(DeconstructError.into())
@ -294,10 +369,10 @@ impl<'a, E, Inner, N> Deconstruct<'a, E> for Prefix<Inner, N>
where
Inner: Deconstruct<'a, E>,
N: PathNode,
E: From<<N as FromStr>::Err>,
E: From<<N as PathItem>::Error>,
E: From<DeconstructError>,
{
fn deconstruct(s: &'a str) -> Result<Self, E>
fn deconstruct(s: &'a [u8]) -> Result<Self, E>
where
Self: Sized + 'a,
{
@ -320,8 +395,13 @@ where
Inner: Construct,
N: PathNode,
{
fn construct(&self) -> String {
self.inner.construct() + "." + &escape(N::NAME) + "." + &escape(&self.segment.to_string())
fn construct(&self) -> Vec<u8> {
let mut vec = self.inner.construct();
vec.extend_from_slice(b".");
vec.extend_from_slice(&escape(N::NAME));
vec.extend_from_slice(b".");
vec.extend_from_slice(&escape(&self.segment.to_bytes()));
vec
}
}
@ -330,8 +410,11 @@ where
Inner: Construct,
F: PathField,
{
fn construct(&self) -> String {
self.inner.construct() + "." + &escape(&self.segment.to_string())
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
}
}
@ -340,20 +423,88 @@ where
Inner: Construct,
N: PathNode,
{
fn construct(&self) -> String {
self.inner.construct() + "." + &escape(N::NAME)
fn construct(&self) -> Vec<u8> {
let mut vec = self.inner.construct();
vec.extend_from_slice(b".");
vec.extend_from_slice(&escape(N::NAME));
vec
}
}
const ESCAPE_CHARS: &'static [char] = &['.', '\\'];
const ESCAPE_CHARS: &'static [u8] = &[b'.', b'\\'];
fn rsplit_once_escaped<'a>(s: &'a str) -> Option<(&'a str, &'a str)> {
fn rfind(s: &[u8], item: u8) -> Option<usize> {
s.iter()
.enumerate()
.rev()
.find(|(_, elem)| **elem == item)
.map(|(index, _)| index)
}
fn find_any(s: &[u8], items: &[u8]) -> Option<usize> {
s.iter()
.enumerate()
.find(|(_, elem)| items.contains(*elem))
.map(|(index, _)| index)
}
fn find_exact(s: &[u8], pattern: &[u8]) -> Option<usize> {
s.windows(pattern.len())
.enumerate()
.find(|(_, elem)| *elem == pattern)
.map(|(index, _)| index)
}
struct Split<'a, 'b>(Option<&'a [u8]>, &'b [u8]);
impl<'a, 'b> Iterator for Split<'a, 'b> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if let Some(source) = self.0.take() {
if let Some(index) = find_exact(source, self.1) {
let (left, right) = source.split_at(index);
self.0 = Some(&right[self.1.len()..]);
return Some(left);
} else {
return Some(source);
}
}
None
}
}
fn split<'a, 'b>(s: &'a [u8], items: &'b [u8]) -> Split<'a, 'b> {
Split(Some(s), items)
}
fn join<I, T, U>(iterator: I, joiner: &[U]) -> Vec<U>
where
I: Iterator<Item = T>,
T: Borrow<[U]>,
U: Clone + Debug,
{
iterator
.fold((true, Vec::new()), |(first, mut vec), elem| {
if first {
vec.extend_from_slice(&elem.borrow());
} else {
vec.extend_from_slice(joiner);
vec.extend_from_slice(&elem.borrow());
}
(false, vec)
})
.1
}
fn rsplit_once_escaped<'a>(s: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> {
let mut position: usize = s.len();
while let Some(index) = s[..position].rfind('.') {
while let Some(index) = rfind(&s[..position], b'.') {
let mut escaped = false;
for prev in (0..index).rev() {
if s.get(prev..prev + 1) != Some("\\") {
if s.get(prev..prev + 1) != Some(b"\\") {
break;
}
@ -371,27 +522,27 @@ fn rsplit_once_escaped<'a>(s: &'a str) -> Option<(&'a str, &'a str)> {
None
}
fn escape(s: &str) -> Cow<'_, str> {
if s.find(ESCAPE_CHARS).is_some() {
let v = s
.split('.')
.map(|part| part.split('\\').collect::<Vec<&str>>().join("\\\\"))
.collect::<Vec<String>>();
fn escape(s: &[u8]) -> Cow<'_, [u8]> {
if find_any(s, ESCAPE_CHARS).is_some() {
let v = join(
split(s, b".").map(|part| join(split(part, b"\\"), b"\\\\")),
b"\\.",
);
return Cow::Owned(v.join("\\."));
return Cow::Owned(v);
}
Cow::Borrowed(s)
}
fn unescape(s: &str) -> Cow<'_, str> {
if s.find(ESCAPE_CHARS).is_some() {
let v = s
.split("\\\\")
.map(|part| part.split("\\.").collect::<Vec<&str>>().join("."))
.collect::<Vec<String>>();
fn unescape(s: &[u8]) -> Cow<'_, [u8]> {
if find_any(s, ESCAPE_CHARS).is_some() {
let v = join(
split(s, b"\\\\").map(|part| join(split(part, b"\\."), b".")),
b"\\",
);
return Cow::Owned(v.join("\\"));
return Cow::Owned(v);
}
Cow::Borrowed(s)
@ -399,16 +550,13 @@ fn unescape(s: &str) -> Cow<'_, str> {
#[cfg(feature = "test")]
pub mod test {
use super::{PathField, PathNode};
use std::{
fmt::{Debug, Display},
str::FromStr,
};
use super::{PathField, PathItem, PathNode};
use std::fmt::Debug;
pub fn test_pathnode<N>(path_node: N)
where
N: PathNode + Debug + Eq,
<N as FromStr>::Err: Debug,
<N as PathItem>::Error: Debug,
{
test_roundtrip(path_node);
}
@ -416,126 +564,130 @@ pub mod test {
pub fn test_field<F>(path_field: F)
where
F: PathField + Debug + Eq,
<F as FromStr>::Err: Debug,
<F as PathItem>::Error: Debug,
{
test_roundtrip(path_field)
}
fn test_roundtrip<T>(item: T)
where
T: Display + FromStr + Debug + Eq,
<T as FromStr>::Err: Debug,
T: PathItem + Debug + Eq,
<T as PathItem>::Error: Debug,
{
let string = item.to_string();
let round_trip = string.parse::<T>().unwrap();
let plus_one = round_trip.to_string();
let vec = item.to_bytes();
let round_trip = T::parse(&vec).unwrap();
let plus_one = round_trip.to_bytes();
assert_eq!(round_trip, item);
assert_eq!(string, plus_one);
assert_eq!(vec, plus_one);
}
}
#[cfg(test)]
mod tests {
use std::{fmt::Display, str::FromStr};
use std::{
fmt::Display,
num::ParseIntError,
str::{from_utf8, Utf8Error},
};
use super::{escape, rsplit_once_escaped, unescape, PathField, PathGen, PathNode};
use super::{escape, rsplit_once_escaped, unescape, PathField, PathGen, PathItem, PathNode};
const ROOT: PathGen<'static> = PathGen::new("test-root");
#[test]
fn splits_on_dot() {
let input = "left of dot . right of dot";
let input = b"left of dot . right of dot";
let (left, right) = rsplit_once_escaped(input).unwrap();
assert_eq!(left, "left of dot ");
assert_eq!(right, " right of dot");
assert_eq!(left, b"left of dot ");
assert_eq!(right, b" right of dot");
}
#[test]
fn splits_on_ending_dot() {
let input = "left of dot .";
let input = b"left of dot .";
let (left, right) = rsplit_once_escaped(input).unwrap();
assert_eq!(left, "left of dot ");
assert_eq!(right, "");
assert_eq!(left, b"left of dot ");
assert_eq!(right, b"");
}
#[test]
fn splits_on_starting_dot() {
let input = ". right of dot";
let input = b". right of dot";
let (left, right) = rsplit_once_escaped(input).unwrap();
assert_eq!(left, "");
assert_eq!(right, " right of dot");
assert_eq!(left, b"");
assert_eq!(right, b" right of dot");
}
#[test]
fn splits_last_dot() {
let input = "left of dots . between dots . right of dots";
let input = b"left of dots . between dots . right of dots";
let (left, right) = rsplit_once_escaped(input).unwrap();
assert_eq!(left, "left of dots . between dots ");
assert_eq!(right, " right of dots");
assert_eq!(left, b"left of dots . between dots ");
assert_eq!(right, b" right of dots");
}
#[test]
fn doesnt_split_escaped_dot() {
let input = "left of dot \\. right of dot";
let input = b"left of dot \\. right of dot";
let opt = rsplit_once_escaped(input);
assert!(opt.is_none());
}
#[test]
fn splits_unescaped_dot_with_preceding_escaped_dots() {
let input = "left of dots . between dots \\. right of dots";
let input = b"left of dots . between dots \\. right of dots";
let (left, right) = rsplit_once_escaped(input).unwrap();
assert_eq!(left, "left of dots ");
assert_eq!(right, " between dots \\. right of dots");
assert_eq!(left, b"left of dots ");
assert_eq!(right, b" between dots \\. right of dots");
}
#[test]
fn splits_unescaped_dot_with_preceding_backslashes() {
let input = "left of dots . between dots \\\\. right of dots";
let input = b"left of dots . between dots \\\\. right of dots";
let (left, right) = rsplit_once_escaped(input).unwrap();
assert_eq!(left, "left of dots . between dots \\\\");
assert_eq!(right, " right of dots");
assert_eq!(left, b"left of dots . between dots \\\\");
assert_eq!(right, b" right of dots");
}
#[test]
fn splits_unescaped_dot_with_preceding_escaped_dots_with_preceding_backslashes() {
let input = "left of dots . between dots \\\\\\. right of dots";
let input = b"left of dots . between dots \\\\\\. right of dots";
let (left, right) = rsplit_once_escaped(input).unwrap();
assert_eq!(left, "left of dots ");
assert_eq!(right, " between dots \\\\\\. right of dots");
assert_eq!(left, b"left of dots ");
assert_eq!(right, b" between dots \\\\\\. right of dots");
}
#[test]
fn escape_output() {
let inputs = [
let inputs: [(&[u8], &[u8]); 13] = [
(
"..\\.\\.\\..\\\\\\...",
"\\.\\.\\\\\\.\\\\\\.\\\\\\.\\.\\\\\\\\\\\\\\.\\.\\.",
b"..\\.\\.\\..\\\\\\...",
b"\\.\\.\\\\\\.\\\\\\.\\\\\\.\\.\\\\\\\\\\\\\\.\\.\\.",
),
(".", "\\."),
("\\", "\\\\"),
("\\.", "\\\\\\."),
(".\\", "\\.\\\\"),
(".\\\\\\", "\\.\\\\\\\\\\\\"),
("\\\\\\.", "\\\\\\\\\\\\\\."),
("...\\", "\\.\\.\\.\\\\"),
("\\...", "\\\\\\.\\.\\."),
("Some text with a . in it", "Some text with a \\. in it"),
("Some text with a \\ in it", "Some text with a \\\\ in it"),
(b".", b"\\."),
(b"\\", b"\\\\"),
(b"\\.", b"\\\\\\."),
(b".\\", b"\\.\\\\"),
(b".\\\\\\", b"\\.\\\\\\\\\\\\"),
(b"\\\\\\.", b"\\\\\\\\\\\\\\."),
(b"...\\", b"\\.\\.\\.\\\\"),
(b"\\...", b"\\\\\\.\\.\\."),
(b"Some text with a . in it", b"Some text with a \\. in it"),
(b"Some text with a \\ in it", b"Some text with a \\\\ in it"),
(
"Some text with a \\ and a . in it",
"Some text with a \\\\ and a \\. in it",
b"Some text with a \\ and a . in it",
b"Some text with a \\\\ and a \\. in it",
),
(
"Some text with a . and a \\ in it",
"Some text with a \\. and a \\\\ in it",
b"Some text with a . and a \\ in it",
b"Some text with a \\. and a \\\\ in it",
),
];
for (start, escaped) in inputs {
assert_eq!(&escape(start), escaped);
assert_eq!(&unescape(escaped), start);
assert_eq!(escape(start).as_ref(), escaped);
assert_eq!(unescape(escaped).as_ref(), start);
}
}
@ -545,9 +697,9 @@ mod tests {
.push(Deck("deck-id".to_owned()))
.push(Key(5))
.field(CommandField)
.construct();
.to_bytes();
assert_eq!(&s, "test-root.deck.deck-id.key.5.command")
assert_eq!(&s, b"test-root.deck.deck-id.key.5.command")
}
#[test]
@ -555,9 +707,9 @@ mod tests {
let s = ROOT
.push(Deck("deck-id".to_owned()))
.prefix::<Key>()
.construct();
.to_bytes();
assert_eq!(&s, "test-root.deck.deck-id.key")
assert_eq!(&s, b"test-root.deck.deck-id.key")
}
#[test]
@ -565,9 +717,9 @@ mod tests {
let s = ROOT
.push(Deck("deck.id".to_owned()))
.prefix::<Key>()
.construct();
.to_bytes();
assert_eq!(&s, "test-root.deck.deck\\.id.key")
assert_eq!(&s, b"test-root.deck.deck\\.id.key")
}
#[test]
@ -577,7 +729,7 @@ mod tests {
.push::<Key>()
.field::<CommandField>();
let s = "test-root.deck.deck-id.key.5.command";
let s = b"test-root.deck.deck-id.key.5.command";
let path = parser.parse::<Box<dyn std::error::Error>>(s).unwrap();
@ -593,7 +745,7 @@ mod tests {
fn deconstruct_prefix() {
let parser = PathGen::parser().push::<Deck>().prefix::<Key>();
let s = "test-root.deck.deck-id.key";
let s = b"test-root.deck.deck-id.key";
let path = parser.parse::<Box<dyn std::error::Error>>(s).unwrap();
@ -604,7 +756,7 @@ mod tests {
fn deconstruct_escaped_prefix() {
let parser = PathGen::parser().push::<Deck>().prefix::<Key>();
let s = "test-root.deck.deck\\.id.key";
let s = b"test-root.deck.deck\\.id.key";
let path = parser.parse::<Box<dyn std::error::Error>>(s).unwrap();
@ -627,7 +779,7 @@ mod tests {
#[derive(Debug)]
struct CommandFieldError;
impl std::fmt::Display for CommandFieldError {
impl Display for CommandFieldError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "string did not match 'command'")
}
@ -635,59 +787,62 @@ mod tests {
impl std::error::Error for CommandFieldError {}
impl PathItem for Deck {
type Error = Utf8Error;
fn parse<'a>(bytes: &'a [u8]) -> Result<Self, Self::Error>
where
Self: Sized + 'a,
{
Ok(Deck(from_utf8(bytes)?.to_string()))
}
fn to_bytes(&self) -> Vec<u8> {
self.0.as_bytes().to_vec()
}
}
impl PathItem for Key {
type Error = ParseIntError;
fn parse<'a>(bytes: &'a [u8]) -> Result<Self, Self::Error>
where
Self: Sized + 'a,
{
Ok(Key(String::from_utf8_lossy(bytes).parse()?))
}
fn to_bytes(&self) -> Vec<u8> {
self.0.to_string().into_bytes()
}
}
impl PathItem for CommandField {
type Error = CommandFieldError;
fn parse<'a>(bytes: &'a [u8]) -> Result<Self, Self::Error>
where
Self: Sized + 'a,
{
if bytes == b"command" {
return Ok(CommandField);
}
Err(CommandFieldError)
}
fn to_bytes(&self) -> Vec<u8> {
b"command".to_vec()
}
}
impl PathNode for Deck {
const NAME: &'static str = "deck";
const NAME: &'static [u8] = b"deck";
}
impl PathNode for Key {
const NAME: &'static str = "key";
const NAME: &'static [u8] = b"key";
}
impl PathField for CommandField {}
impl FromStr for Deck {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Deck(s.to_owned()))
}
}
impl FromStr for Key {
type Err = <u8 as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Key(s.parse()?))
}
}
impl FromStr for CommandField {
type Err = CommandFieldError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "command" {
Ok(CommandField)
} else {
Err(CommandFieldError)
}
}
}
impl Display for Deck {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Display for CommandField {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "command")
}
}
}