Compare commits
No commits in common. "abc27adea42e4bc9038d02980efbb5a4d2073f55" and "b0bd343188c604881df74c8b53950a322d279fed" have entirely different histories.
abc27adea4
...
b0bd343188
|
@ -2,7 +2,7 @@
|
|||
name = "nettext"
|
||||
description = "A text-based data format for cryptographic network protocols"
|
||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||
version = "0.3.0"
|
||||
version = "0.2.3"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0"
|
||||
readme = "README.md"
|
||||
|
|
43
README.md
43
README.md
|
@ -15,8 +15,10 @@ A term can be of any of the following kinds:
|
|||
|
||||
- a string, which may contain only ASCII alphanumeric terms and `.-_*?`
|
||||
- a dict, which maps strings (as defined above) to any term type
|
||||
- a list, which may contain any number of any kind of terms (can be mixed)
|
||||
- a sequence, consistuted of at least two of the above (can be mixed), simply separated by whitespace; sequences cannot be nested
|
||||
- a list, which is a consecutive sequence of at least 2 strings or dicts (can be mixed), simply separated by whitespace
|
||||
|
||||
Nested lists can be represented using a special dictionnary with a single key, `.`,
|
||||
for instance `TEST a { . = 0 4 2 1 9 7 0 } c`.
|
||||
|
||||
Dicts are represented as follows:
|
||||
|
||||
|
@ -27,13 +29,7 @@ Dicts are represented as follows:
|
|||
}
|
||||
```
|
||||
|
||||
Dicts are represented as follows:
|
||||
|
||||
```
|
||||
[ term1, term2 ]
|
||||
```
|
||||
|
||||
Sequences are represented as follows:
|
||||
Lists are represented as follows:
|
||||
|
||||
```
|
||||
term1 term2 term3
|
||||
|
@ -42,26 +38,21 @@ term1 term2 term3
|
|||
As a consequence, complex data structures can be defined as follows:
|
||||
|
||||
```
|
||||
SEND MESSAGE {
|
||||
SENDTO alex {
|
||||
topic = blah,
|
||||
to = [
|
||||
TOPIC hello,
|
||||
USER john
|
||||
],
|
||||
body = blah blah
|
||||
}
|
||||
```
|
||||
|
||||
The raw representation of a parsed dict or sequence is retained for hashing purposes.
|
||||
The raw representation of a parsed dict or list is retained for hashing purposes.
|
||||
It in the sequence of bytes, in the encoded string, trimmed from whitespace at extremities,
|
||||
that represents the encoded dict or sequence in that string.
|
||||
that represents the encoded dict or list in that string.
|
||||
|
||||
In the complex stance example above, here are the sequence and dicts and their raw representation:
|
||||
In the complex stance example above, here are the lists and dicts and their raw representation:
|
||||
|
||||
- the toplevel term is a sequence, whose raw representation is the entire encoded string (assuming no whitespace at beginning or end)
|
||||
- the third term of the sequence is a dict, whose raw representation starts at `{` and ends at `}`
|
||||
- the second mapping of the dict is a list, whose raw representation starts at `[` and ends at `]`
|
||||
- the third mapping of the dict is a sequence, whose raw representation is exactly `blah blah`.
|
||||
- the toplevel term is a list, whose raw representation is the entire encoded string (assuming no whitespace at beginning or end)
|
||||
- the third term of the list is a dict, whose raw representation starts at `{` and ends at `}`
|
||||
- the second mapping of the dict is a list, whose raw representation is exactly `blah blah`.
|
||||
|
||||
Since strings cannot contain whitespace, they are always equivalent to their raw representation.
|
||||
|
||||
|
@ -70,13 +61,13 @@ Since strings cannot contain whitespace, they are always equivalent to their raw
|
|||
Terms can be interpreted in a number of different ways, depending on the context:
|
||||
|
||||
- RAW: the term is interpreted as its raw encoding (see above)
|
||||
- STRING: if the term is a string or a sequence composed exclusively of strings, the term is interpreted as its raw encoding
|
||||
- VARIANT: if the term is a sequence whose first item is a string, it is interpreted as a variant with the following properties:
|
||||
- STRING: if the term is a string or a list composed exclusively of strings, the term is interpreted as its raw encoding
|
||||
- VARIANT: if the term is a list whose first item is a string, it is interpreted as a variant with the following properties:
|
||||
- a discriminator (the first item)
|
||||
- a value, which is either the second item in case there are only two items, or the sequence composed of all items starting from the second if there are more than two
|
||||
- a value, which is either the second item in case there are only two items, or the list composed of all items starting from the second if there are more than two
|
||||
- DICT: if the term is a dict, interpret it as such
|
||||
- LIST: if the term is a list, interpret it as such
|
||||
- SEQ: if the term is a string, a list, or a dict, interpret it as a sequence composed of that single term. Otherwise, the term is a sequence, interpret it as a sequence of terms.
|
||||
- LIST: if the term is a string or a dict, interpret it as a list composed of that single term. Otherwise, the term is a list, interpret it as a list of terms.
|
||||
- NESTED: if the term is a dict with a single key `.`, interpret it as the term associated to that key
|
||||
|
||||
## Data mappings
|
||||
|
||||
|
|
|
@ -8,11 +8,8 @@ use nom::{
|
|||
IResult, InputLength,
|
||||
};
|
||||
|
||||
use crate::dec::{AnyTerm, DecodeError, NonSeqTerm, Term};
|
||||
use crate::{
|
||||
is_string_char, is_whitespace, DICT_ASSIGN, DICT_CLOSE, DICT_DELIM, DICT_OPEN, LIST_CLOSE,
|
||||
LIST_DELIM, LIST_OPEN,
|
||||
};
|
||||
use crate::dec::{AnyTerm, DecodeError, NonListTerm, Term};
|
||||
use crate::{is_string_char, is_whitespace, DICT_ASSIGN, DICT_CLOSE, DICT_DELIM, DICT_OPEN};
|
||||
|
||||
// ----
|
||||
|
||||
|
@ -28,22 +25,21 @@ pub fn decode(input: &[u8]) -> std::result::Result<Term<'_, '_>, DecodeError<'_>
|
|||
|
||||
fn decode_term(input: &[u8]) -> IResult<&'_ [u8], AnyTerm<'_, '_>> {
|
||||
let (start, _) = take_while(is_whitespace)(input)?;
|
||||
let (rest, seq) = separated_list1(take_while1(is_whitespace), decode_nonseq_term)(start)?;
|
||||
let (rest, list) = separated_list1(take_while1(is_whitespace), decode_nonlist_term)(start)?;
|
||||
|
||||
if seq.len() == 1 {
|
||||
Ok((rest, seq.into_iter().next().unwrap().into()))
|
||||
if list.len() == 1 {
|
||||
Ok((rest, list.into_iter().next().unwrap().into()))
|
||||
} else {
|
||||
let raw_len = start.input_len() - rest.input_len();
|
||||
let seq_raw = &start[..raw_len];
|
||||
Ok((rest, AnyTerm::Seq(seq_raw, seq)))
|
||||
let list_raw = &start[..raw_len];
|
||||
Ok((rest, AnyTerm::List(list_raw, list)))
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_nonseq_term(input: &[u8]) -> IResult<&'_ [u8], NonSeqTerm<'_, '_>> {
|
||||
fn decode_nonlist_term(input: &[u8]) -> IResult<&'_ [u8], NonListTerm<'_, '_>> {
|
||||
let (rest, term) = alt((
|
||||
map(decode_str, NonSeqTerm::Str),
|
||||
map(decode_dict, |(raw, d)| NonSeqTerm::Dict(raw, d)),
|
||||
map(decode_list, |(raw, l)| NonSeqTerm::List(raw, l)),
|
||||
map(decode_str, NonListTerm::Str),
|
||||
map(decode_dict, |(raw, d)| NonListTerm::Dict(raw, d)),
|
||||
))(input)?;
|
||||
Ok((rest, term))
|
||||
}
|
||||
|
@ -85,27 +81,6 @@ fn decode_dict_item(d: &[u8]) -> IResult<&'_ [u8], (&'_ [u8], AnyTerm<'_, '_>)>
|
|||
Ok((d, (key, value)))
|
||||
}
|
||||
|
||||
type ListType<'a> = (&'a [u8], Vec<AnyTerm<'a, 'a>>);
|
||||
|
||||
fn decode_list(list_begin: &[u8]) -> IResult<&'_ [u8], ListType<'_>> {
|
||||
let (d, _) = tag(&[LIST_OPEN][..])(list_begin)?;
|
||||
let (d, list) = separated_list0(list_separator, decode_term)(d)?;
|
||||
let (d, _) = opt(list_separator)(d)?;
|
||||
let (d, _) = take_while(is_whitespace)(d)?;
|
||||
let (list_end, _) = tag(&[LIST_CLOSE][..])(d)?;
|
||||
|
||||
let raw_len = list_begin.input_len() - list_end.input_len();
|
||||
let list_raw = &list_begin[..raw_len];
|
||||
|
||||
Ok((list_end, (list_raw, list)))
|
||||
}
|
||||
|
||||
fn list_separator(d: &[u8]) -> IResult<&'_ [u8], ()> {
|
||||
let (d, _) = take_while(is_whitespace)(d)?;
|
||||
let (d, _) = tag(&[LIST_DELIM][..])(d)?;
|
||||
Ok((d, ()))
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -119,17 +94,17 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn seq_of_str_str() {
|
||||
fn list_of_str_str() {
|
||||
let bytes = b" plop plap plip ploup ";
|
||||
assert_eq!(
|
||||
decode(bytes),
|
||||
Ok(AnyTerm::Seq(
|
||||
Ok(AnyTerm::List(
|
||||
b"plop plap plip ploup",
|
||||
vec![
|
||||
NonSeqTerm::Str(b"plop"),
|
||||
NonSeqTerm::Str(b"plap"),
|
||||
NonSeqTerm::Str(b"plip"),
|
||||
NonSeqTerm::Str(b"ploup"),
|
||||
NonListTerm::Str(b"plop"),
|
||||
NonListTerm::Str(b"plap"),
|
||||
NonListTerm::Str(b"plip"),
|
||||
NonListTerm::Str(b"ploup"),
|
||||
]
|
||||
)
|
||||
.into())
|
||||
|
@ -147,9 +122,9 @@ mod tests {
|
|||
(&b"aze"[..], AnyTerm::Str(b"hello")),
|
||||
(
|
||||
&b"by"[..],
|
||||
AnyTerm::Seq(
|
||||
AnyTerm::List(
|
||||
b"bojzkz pipo",
|
||||
vec![NonSeqTerm::Str(b"bojzkz"), NonSeqTerm::Str(b"pipo")]
|
||||
vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")]
|
||||
)
|
||||
),
|
||||
(&b"ccde"[..], AnyTerm::Str(b"ke")),
|
||||
|
@ -172,9 +147,9 @@ mod tests {
|
|||
(&b"aze"[..], AnyTerm::Str(b"hello")),
|
||||
(
|
||||
&b"by"[..],
|
||||
AnyTerm::Seq(
|
||||
AnyTerm::List(
|
||||
b"bojzkz pipo",
|
||||
vec![NonSeqTerm::Str(b"bojzkz"), NonSeqTerm::Str(b"pipo")]
|
||||
vec![NonListTerm::Str(b"bojzkz"), NonListTerm::Str(b"pipo")]
|
||||
)
|
||||
),
|
||||
(&b"ccde"[..], AnyTerm::Str(b"ke")),
|
||||
|
@ -186,56 +161,14 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_list() {
|
||||
let bytes = b" [ hello, bojzkz pipo, ke ] ";
|
||||
assert_eq!(
|
||||
decode(bytes),
|
||||
Ok(AnyTerm::List(
|
||||
b"[ hello, bojzkz pipo, ke ]",
|
||||
[
|
||||
AnyTerm::Str(b"hello"),
|
||||
AnyTerm::Seq(
|
||||
b"bojzkz pipo",
|
||||
vec![NonSeqTerm::Str(b"bojzkz"), NonSeqTerm::Str(b"pipo")]
|
||||
),
|
||||
AnyTerm::Str(b"ke"),
|
||||
]
|
||||
.to_vec()
|
||||
)
|
||||
.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_list_2() {
|
||||
let bytes = b" [ hello, bojzkz pipo , ke , ] ";
|
||||
assert_eq!(
|
||||
decode(bytes),
|
||||
Ok(AnyTerm::List(
|
||||
b"[ hello, bojzkz pipo , ke , ]",
|
||||
[
|
||||
AnyTerm::Str(b"hello"),
|
||||
AnyTerm::Seq(
|
||||
b"bojzkz pipo",
|
||||
vec![NonSeqTerm::Str(b"bojzkz"), NonSeqTerm::Str(b"pipo")]
|
||||
),
|
||||
AnyTerm::Str(b"ke"),
|
||||
]
|
||||
.to_vec()
|
||||
)
|
||||
.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_world_1() {
|
||||
let bytes = b"HEAD alexpubkey";
|
||||
assert_eq!(
|
||||
decode(bytes),
|
||||
Ok(AnyTerm::Seq(
|
||||
Ok(AnyTerm::List(
|
||||
b"HEAD alexpubkey",
|
||||
vec![NonSeqTerm::Str(b"HEAD"), NonSeqTerm::Str(b"alexpubkey")]
|
||||
vec![NonListTerm::Str(b"HEAD"), NonListTerm::Str(b"alexpubkey")]
|
||||
)
|
||||
.into()),
|
||||
);
|
||||
|
@ -246,22 +179,22 @@ mod tests {
|
|||
let bytes = b"STANCE sthash stsign { author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }";
|
||||
assert_eq!(
|
||||
decode(bytes),
|
||||
Ok(AnyTerm::Seq(
|
||||
Ok(AnyTerm::List(
|
||||
&bytes[..],
|
||||
vec![
|
||||
NonSeqTerm::Str(b"STANCE"),
|
||||
NonSeqTerm::Str(b"sthash"),
|
||||
NonSeqTerm::Str(b"stsign"),
|
||||
NonSeqTerm::Dict(b"{ author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }",
|
||||
NonListTerm::Str(b"STANCE"),
|
||||
NonListTerm::Str(b"sthash"),
|
||||
NonListTerm::Str(b"stsign"),
|
||||
NonListTerm::Dict(b"{ author = alexpubkey, height = 12, parent = parenthash, data = MESSAGE { text = hello } }",
|
||||
[
|
||||
(&b"author"[..], AnyTerm::Str(b"alexpubkey")),
|
||||
(&b"height"[..], AnyTerm::Str(b"12")),
|
||||
(&b"parent"[..], AnyTerm::Str(b"parenthash")),
|
||||
(&b"data"[..], AnyTerm::Seq(
|
||||
(&b"data"[..], AnyTerm::List(
|
||||
b"MESSAGE { text = hello }",
|
||||
vec![
|
||||
NonSeqTerm::Str(b"MESSAGE"),
|
||||
NonSeqTerm::Dict(
|
||||
NonListTerm::Str(b"MESSAGE"),
|
||||
NonListTerm::Dict(
|
||||
b"{ text = hello }",
|
||||
[
|
||||
(&b"text"[..], AnyTerm::Str(b"hello")),
|
||||
|
@ -276,51 +209,4 @@ mod tests {
|
|||
]).into(),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_world_3() {
|
||||
let bytes = b"[ USER john, USER luke, GROUP strategy { owner = USER john, members = [ USER john, USER luke ] } ]";
|
||||
let user_john = AnyTerm::Seq(
|
||||
b"USER john",
|
||||
vec![NonSeqTerm::Str(b"USER"), NonSeqTerm::Str(b"john")],
|
||||
);
|
||||
let user_luke = AnyTerm::Seq(
|
||||
b"USER luke",
|
||||
vec![NonSeqTerm::Str(b"USER"), NonSeqTerm::Str(b"luke")],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decode(bytes),
|
||||
Ok(AnyTerm::List(
|
||||
&bytes[..],
|
||||
vec![
|
||||
user_john.clone(),
|
||||
user_luke.clone(),
|
||||
AnyTerm::Seq(
|
||||
b"GROUP strategy { owner = USER john, members = [ USER john, USER luke ] }",
|
||||
vec![
|
||||
NonSeqTerm::Str(b"GROUP"),
|
||||
NonSeqTerm::Str(b"strategy"),
|
||||
NonSeqTerm::Dict(
|
||||
b"{ owner = USER john, members = [ USER john, USER luke ] }",
|
||||
[
|
||||
(&b"owner"[..], user_john.clone()),
|
||||
(
|
||||
&b"members"[..],
|
||||
AnyTerm::List(
|
||||
b"[ USER john, USER luke ]",
|
||||
vec![user_john, user_luke,]
|
||||
)
|
||||
)
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
.into())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
191
src/dec/mod.rs
191
src/dec/mod.rs
|
@ -8,8 +8,6 @@ use std::collections::HashMap;
|
|||
#[cfg(any(feature = "dryoc"))]
|
||||
use crate::crypto;
|
||||
|
||||
use crate::debug;
|
||||
|
||||
pub use decode::*;
|
||||
pub use error::*;
|
||||
|
||||
|
@ -28,42 +26,34 @@ pub(crate) enum AnyTerm<'a, 'b> {
|
|||
Str(&'a [u8]),
|
||||
Dict(&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
||||
DictRef(&'a [u8], &'b HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
||||
List(&'a [u8], Vec<AnyTerm<'a, 'b>>),
|
||||
ListRef(&'a [u8], &'b [AnyTerm<'a, 'b>]),
|
||||
Seq(&'a [u8], Vec<NonSeqTerm<'a, 'b>>),
|
||||
SeqRef(&'a [u8], &'b [NonSeqTerm<'a, 'b>]),
|
||||
List(&'a [u8], Vec<NonListTerm<'a, 'b>>),
|
||||
ListRef(&'a [u8], &'b [NonListTerm<'a, 'b>]),
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone)]
|
||||
pub(crate) enum NonSeqTerm<'a, 'b> {
|
||||
pub(crate) enum NonListTerm<'a, 'b> {
|
||||
Str(&'a [u8]),
|
||||
Dict(&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
||||
DictRef(&'a [u8], &'b HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
||||
List(&'a [u8], Vec<AnyTerm<'a, 'b>>),
|
||||
ListRef(&'a [u8], &'b [AnyTerm<'a, 'b>]),
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<NonSeqTerm<'a, 'b>> for AnyTerm<'a, 'b> {
|
||||
fn from(x: NonSeqTerm<'a, 'b>) -> AnyTerm<'a, 'b> {
|
||||
impl<'a, 'b> From<NonListTerm<'a, 'b>> for AnyTerm<'a, 'b> {
|
||||
fn from(x: NonListTerm<'a, 'b>) -> AnyTerm<'a, 'b> {
|
||||
match x {
|
||||
NonSeqTerm::Str(s) => AnyTerm::Str(s),
|
||||
NonSeqTerm::Dict(raw, d) => AnyTerm::Dict(raw, d),
|
||||
NonSeqTerm::DictRef(raw, d) => AnyTerm::DictRef(raw, d),
|
||||
NonSeqTerm::List(raw, l) => AnyTerm::List(raw, l),
|
||||
NonSeqTerm::ListRef(raw, l) => AnyTerm::ListRef(raw, l),
|
||||
NonListTerm::Str(s) => AnyTerm::Str(s),
|
||||
NonListTerm::Dict(raw, d) => AnyTerm::Dict(raw, d),
|
||||
NonListTerm::DictRef(raw, d) => AnyTerm::DictRef(raw, d),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> TryFrom<AnyTerm<'a, 'b>> for NonSeqTerm<'a, 'b> {
|
||||
impl<'a, 'b> TryFrom<AnyTerm<'a, 'b>> for NonListTerm<'a, 'b> {
|
||||
type Error = ();
|
||||
fn try_from(x: AnyTerm<'a, 'b>) -> Result<NonSeqTerm<'a, 'b>, ()> {
|
||||
fn try_from(x: AnyTerm<'a, 'b>) -> Result<NonListTerm<'a, 'b>, ()> {
|
||||
match x {
|
||||
AnyTerm::Str(s) => Ok(NonSeqTerm::Str(s)),
|
||||
AnyTerm::Dict(raw, d) => Ok(NonSeqTerm::Dict(raw, d)),
|
||||
AnyTerm::DictRef(raw, d) => Ok(NonSeqTerm::DictRef(raw, d)),
|
||||
AnyTerm::List(raw, l) => Ok(NonSeqTerm::List(raw, l)),
|
||||
AnyTerm::ListRef(raw, l) => Ok(NonSeqTerm::ListRef(raw, l)),
|
||||
AnyTerm::Str(s) => Ok(NonListTerm::Str(s)),
|
||||
AnyTerm::Dict(raw, d) => Ok(NonListTerm::Dict(raw, d)),
|
||||
AnyTerm::DictRef(raw, d) => Ok(NonListTerm::DictRef(raw, d)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
@ -94,20 +84,6 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
self.0.raw()
|
||||
}
|
||||
|
||||
/// Get the term's raw representation as an str
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```
|
||||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term = decode(b"hello { a = x, b = y }").unwrap();
|
||||
/// assert_eq!(term.raw_str().unwrap(), "hello { a = x, b = y }");
|
||||
/// ```
|
||||
pub fn raw_str(&self) -> Result<&'a str, TypeError> {
|
||||
Ok(std::str::from_utf8(self.0.raw())?)
|
||||
}
|
||||
|
||||
/// If the term is a single string, get that string
|
||||
///
|
||||
/// Example:
|
||||
|
@ -128,7 +104,7 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
/// If the term is a single string, or a sequence containing only strings,
|
||||
/// If the term is a single string, or a list containing only strings,
|
||||
/// get its raw representation
|
||||
///
|
||||
/// Example:
|
||||
|
@ -145,16 +121,16 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
pub fn string(&self) -> Result<&'a str, TypeError> {
|
||||
match &self.0 {
|
||||
AnyTerm::Str(s) => Ok(std::str::from_utf8(s)?),
|
||||
AnyTerm::Seq(r, l) if l.iter().all(|x| matches!(x, NonSeqTerm::Str(_))) => {
|
||||
AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => {
|
||||
Ok(std::str::from_utf8(r)?)
|
||||
}
|
||||
_ => Err(TypeError::WrongType("STRING")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a sequence of terms made from this term.
|
||||
/// If it is a str or a dict, returns a seq of a single term.
|
||||
/// If it is a sequence, that's the seq of terms we return.
|
||||
/// Return a list of terms made from this term.
|
||||
/// If it is a str or a dict, returns a list of a single term.
|
||||
/// If it is a list, that's the list of terms we return.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
|
@ -162,26 +138,26 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term1 = decode(b"hello").unwrap();
|
||||
/// let seq1 = term1.seq();
|
||||
/// assert_eq!(seq1.len(), 1);
|
||||
/// assert_eq!(seq1[0].str().unwrap(), "hello");
|
||||
/// let list1 = term1.list();
|
||||
/// assert_eq!(list1.len(), 1);
|
||||
/// assert_eq!(list1[0].str().unwrap(), "hello");
|
||||
///
|
||||
/// let term2 = decode(b"hello world").unwrap();
|
||||
/// let seq2 = term2.seq();
|
||||
/// assert_eq!(seq2.len(), 2);
|
||||
/// assert_eq!(seq2[0].str().unwrap(), "hello");
|
||||
/// assert_eq!(seq2[1].str().unwrap(), "world");
|
||||
/// let list2 = term2.list();
|
||||
/// assert_eq!(list2.len(), 2);
|
||||
/// assert_eq!(list2[0].str().unwrap(), "hello");
|
||||
/// assert_eq!(list2[1].str().unwrap(), "world");
|
||||
/// ```
|
||||
pub fn seq(&self) -> Vec<Term<'a, '_>> {
|
||||
pub fn list(&self) -> Vec<Term<'a, '_>> {
|
||||
match self.0.mkref() {
|
||||
AnyTerm::SeqRef(_r, l) => l.iter().map(|x| Term(x.mkref().into())).collect::<Vec<_>>(),
|
||||
AnyTerm::ListRef(_r, l) => l.iter().map(|x| Term(x.mkref().into())).collect::<Vec<_>>(),
|
||||
x => vec![Term(x)],
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `.seq()`, but deconstructs it in a const length array,
|
||||
/// Same as `.list()`, but deconstructs it in a const length array,
|
||||
/// dynamically checking if there are the correct number of items.
|
||||
/// This allows to directly bind the resulting seq into discrete variables.
|
||||
/// This allows to directly bind the resulting list into discrete variables.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
|
@ -189,24 +165,24 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term1 = decode(b"hello").unwrap();
|
||||
/// let [s1] = term1.seq_of().unwrap();
|
||||
/// let [s1] = term1.list_of().unwrap();
|
||||
/// assert_eq!(s1.str().unwrap(), "hello");
|
||||
///
|
||||
/// let term2 = decode(b"hello world").unwrap();
|
||||
/// let [s2a, s2b] = term2.seq_of().unwrap();
|
||||
/// let [s2a, s2b] = term2.list_of().unwrap();
|
||||
/// assert_eq!(s2a.str().unwrap(), "hello");
|
||||
/// assert_eq!(s2b.str().unwrap(), "world");
|
||||
/// ```
|
||||
pub fn seq_of<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||
let seq = self.seq();
|
||||
let seq_len = seq.len();
|
||||
seq.try_into()
|
||||
.map_err(|_| TypeError::WrongLength(seq_len, N))
|
||||
pub fn list_of<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||
let list = self.list();
|
||||
let list_len = list.len();
|
||||
list.try_into()
|
||||
.map_err(|_| TypeError::WrongLength(list_len, N))
|
||||
}
|
||||
|
||||
/// Same as `.seq_of()`, but only binds the first N-1 terms.
|
||||
/// Same as `.list_of()`, but only binds the first N-1 terms.
|
||||
/// If there are exactly N terms, the last one is bound to the Nth return variable.
|
||||
/// If there are more then N terms, the remaining terms are bound to a new seq term
|
||||
/// If there are more then N terms, the remaining terms are bound to a new list term
|
||||
/// that is returned as the Nth return variable.
|
||||
///
|
||||
/// Example:
|
||||
|
@ -215,21 +191,21 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term1 = decode(b"hello world").unwrap();
|
||||
/// let [s1a, s1b] = term1.seq_of_first().unwrap();
|
||||
/// let [s1a, s1b] = term1.list_of_first().unwrap();
|
||||
/// assert_eq!(s1a.str().unwrap(), "hello");
|
||||
/// assert_eq!(s1b.str().unwrap(), "world");
|
||||
///
|
||||
/// let term2 = decode(b"hello mighty world").unwrap();
|
||||
/// let [s2a, s2b] = term2.seq_of_first().unwrap();
|
||||
/// let [s2a, s2b] = term2.list_of_first().unwrap();
|
||||
/// assert_eq!(s2a.str().unwrap(), "hello");
|
||||
/// assert_eq!(s2b.seq().len(), 2);
|
||||
/// assert_eq!(s2b.list().len(), 2);
|
||||
/// assert_eq!(s2b.raw(), b"mighty world");
|
||||
/// ```
|
||||
pub fn seq_of_first<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||
pub fn list_of_first<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
||||
match self.0.mkref() {
|
||||
AnyTerm::SeqRef(raw, seq) => match seq.len().cmp(&N) {
|
||||
std::cmp::Ordering::Less => Err(TypeError::WrongLength(seq.len(), N)),
|
||||
std::cmp::Ordering::Equal => Ok(seq
|
||||
AnyTerm::ListRef(raw, list) => match list.len().cmp(&N) {
|
||||
std::cmp::Ordering::Less => Err(TypeError::WrongLength(list.len(), N)),
|
||||
std::cmp::Ordering::Equal => Ok(list
|
||||
.iter()
|
||||
.map(|x| Term(x.mkref().into()))
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -237,15 +213,15 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
.unwrap()),
|
||||
std::cmp::Ordering::Greater => {
|
||||
let mut ret = Vec::with_capacity(N);
|
||||
for item in seq[0..N - 1].iter() {
|
||||
for item in list[0..N - 1].iter() {
|
||||
ret.push(Term(item.mkref().into()));
|
||||
}
|
||||
|
||||
let remaining_begin = seq[N - 1].raw().as_ptr() as usize;
|
||||
let remaining_begin = list[N - 1].raw().as_ptr() as usize;
|
||||
let remaining_offset = remaining_begin - raw.as_ptr() as usize;
|
||||
let remaining_raw = &raw[remaining_offset..];
|
||||
|
||||
ret.push(Term(AnyTerm::SeqRef(remaining_raw, &seq[N - 1..])));
|
||||
ret.push(Term(AnyTerm::ListRef(remaining_raw, &list[N - 1..])));
|
||||
|
||||
Ok(ret.try_into().unwrap())
|
||||
}
|
||||
|
@ -285,7 +261,7 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
}
|
||||
|
||||
/// Checks term is a dictionnary whose keys are exactly those supplied,
|
||||
/// and returns the associated values as a seq.
|
||||
/// and returns the associated values as a list.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
|
@ -327,7 +303,7 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
}
|
||||
|
||||
/// Checks term is a dictionnary whose keys are included in those supplied,
|
||||
/// and returns the associated values as a seq of options.
|
||||
/// and returns the associated values as a list of options.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
|
@ -363,23 +339,28 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the term is a list, and if so, return its elements in a vec.
|
||||
/// Checks term is a dictionary with a single key `.`,
|
||||
/// and returns the associated value.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```
|
||||
/// use nettext::dec::decode;
|
||||
///
|
||||
/// let term2 = decode(b"[ hello, world ]").unwrap();
|
||||
/// let seq2 = term2.list().unwrap();
|
||||
/// assert_eq!(seq2.len(), 2);
|
||||
/// assert_eq!(seq2[0].str().unwrap(), "hello");
|
||||
/// assert_eq!(seq2[1].str().unwrap(), "world");
|
||||
/// let term = decode(b"{ . = a b c d e }").unwrap();
|
||||
/// assert_eq!(term.nested().unwrap().raw(), b"a b c d e");
|
||||
/// ```
|
||||
pub fn list(&self) -> Result<Vec<Term<'a, '_>>, TypeError> {
|
||||
pub fn nested(&self) -> Result<Term<'a, '_>, TypeError> {
|
||||
match self.0.mkref() {
|
||||
AnyTerm::ListRef(_r, l) => Ok(l.iter().map(|x| Term(x.mkref().into())).collect::<Vec<_>>()),
|
||||
_ => Err(TypeError::WrongType("LIST")),
|
||||
AnyTerm::DictRef(_, d) if d.len() == 1 => {
|
||||
let (k, v) = d.iter().next().unwrap();
|
||||
if k != b"." {
|
||||
Err(TypeError::WrongType("NESTED"))
|
||||
} else {
|
||||
Ok(Term(v.mkref()))
|
||||
}
|
||||
}
|
||||
_ => Err(TypeError::WrongType("NESTED")),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,10 +405,10 @@ impl<'a, 'b> Term<'a, 'b> {
|
|||
decode(encoded)
|
||||
}
|
||||
}
|
||||
AnyTerm::SeqRef(_, seq) => {
|
||||
AnyTerm::ListRef(_, list) => {
|
||||
let mut ret = Vec::with_capacity(128);
|
||||
for term in seq.iter() {
|
||||
if let NonSeqTerm::Str(encoded) = term {
|
||||
for term in list.iter() {
|
||||
if let NonListTerm::Str(encoded) = term {
|
||||
ret.extend(decode(encoded)?)
|
||||
} else {
|
||||
return Err(TypeError::WrongType("BYTES"));
|
||||
|
@ -514,9 +495,7 @@ impl<'a, 'b> AnyTerm<'a, 'b> {
|
|||
AnyTerm::Dict(r, _)
|
||||
| AnyTerm::DictRef(r, _)
|
||||
| AnyTerm::List(r, _)
|
||||
| AnyTerm::ListRef(r, _)
|
||||
| AnyTerm::Seq(r, _)
|
||||
| AnyTerm::SeqRef(r, _) => r,
|
||||
| AnyTerm::ListRef(r, _) => r,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -525,30 +504,25 @@ impl<'a, 'b> AnyTerm<'a, 'b> {
|
|||
AnyTerm::Str(s) => AnyTerm::Str(s),
|
||||
AnyTerm::Dict(r, d) => AnyTerm::DictRef(r, d),
|
||||
AnyTerm::DictRef(r, d) => AnyTerm::DictRef(r, d),
|
||||
AnyTerm::List(r, l) => AnyTerm::ListRef(r, l),
|
||||
AnyTerm::List(r, l) => AnyTerm::ListRef(r, &l[..]),
|
||||
AnyTerm::ListRef(r, l) => AnyTerm::ListRef(r, l),
|
||||
AnyTerm::Seq(r, l) => AnyTerm::SeqRef(r, &l[..]),
|
||||
AnyTerm::SeqRef(r, l) => AnyTerm::SeqRef(r, l),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> NonSeqTerm<'a, 'b> {
|
||||
impl<'a, 'b> NonListTerm<'a, 'b> {
|
||||
fn raw(&self) -> &'a [u8] {
|
||||
match &self {
|
||||
NonSeqTerm::Str(s) => s,
|
||||
NonSeqTerm::Dict(r, _) | NonSeqTerm::DictRef(r, _) => r,
|
||||
NonSeqTerm::List(r, _) | NonSeqTerm::ListRef(r, _) => r,
|
||||
NonListTerm::Str(s) => s,
|
||||
NonListTerm::Dict(r, _) | NonListTerm::DictRef(r, _) => r,
|
||||
}
|
||||
}
|
||||
|
||||
fn mkref(&self) -> NonSeqTerm<'a, '_> {
|
||||
fn mkref(&self) -> NonListTerm<'a, '_> {
|
||||
match &self {
|
||||
NonSeqTerm::Str(s) => NonSeqTerm::Str(s),
|
||||
NonSeqTerm::Dict(r, d) => NonSeqTerm::DictRef(r, d),
|
||||
NonSeqTerm::DictRef(r, d) => NonSeqTerm::DictRef(r, d),
|
||||
NonSeqTerm::List(r, l) => NonSeqTerm::ListRef(r, l),
|
||||
NonSeqTerm::ListRef(r, l) => NonSeqTerm::ListRef(r, l),
|
||||
NonListTerm::Str(s) => NonListTerm::Str(s),
|
||||
NonListTerm::Dict(r, d) => NonListTerm::DictRef(r, d),
|
||||
NonListTerm::DictRef(r, d) => NonListTerm::DictRef(r, d),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -573,6 +547,10 @@ impl<'a, 'b> std::fmt::Display for Term<'a, 'b> {
|
|||
|
||||
// ---- DEBUG REPR ----
|
||||
|
||||
pub(crate) fn debug(x: &[u8]) -> &str {
|
||||
std::str::from_utf8(x).unwrap_or("<invalid ascii>")
|
||||
}
|
||||
|
||||
impl<'a, 'b> std::fmt::Debug for AnyTerm<'a, 'b> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
match self.mkref() {
|
||||
|
@ -591,19 +569,12 @@ impl<'a, 'b> std::fmt::Debug for AnyTerm<'a, 'b> {
|
|||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
AnyTerm::SeqRef(raw, l) => {
|
||||
write!(f, "Seq[`{}`", debug(raw))?;
|
||||
for i in l.iter() {
|
||||
write!(f, "\n {:?}", i)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> std::fmt::Debug for NonSeqTerm<'a, 'b> {
|
||||
impl<'a, 'b> std::fmt::Debug for NonListTerm<'a, 'b> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
AnyTerm::from(self.mkref()).fmt(f)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ pub enum Error {
|
|||
#[error(display = "Duplicate key: {}", _0)]
|
||||
DuplicateKey(String),
|
||||
#[error(
|
||||
display = "Refusing to build nested sequencess with seq(), use either seq_flatten() or seq_nested()"
|
||||
display = "Refusing to build nested lists with list(), use either list_flatten() or list_nested()"
|
||||
)]
|
||||
SeqInSeq,
|
||||
ListInList,
|
||||
}
|
||||
|
|
176
src/enc/mod.rs
176
src/enc/mod.rs
|
@ -5,7 +5,7 @@
|
|||
//! ```
|
||||
//! use nettext::enc::*;
|
||||
//!
|
||||
//! let nettext_encoding = seq([
|
||||
//! let nettext_encoding = list([
|
||||
//! string("CALL").unwrap(),
|
||||
//! string("myfunction").unwrap(),
|
||||
//! dict([
|
||||
|
@ -35,7 +35,6 @@ enum T<'a> {
|
|||
OwnedStr(Vec<u8>),
|
||||
Dict(HashMap<Cow<'a, [u8]>, T<'a>>),
|
||||
List(Vec<T<'a>>),
|
||||
Seq(Vec<T<'a>>),
|
||||
}
|
||||
|
||||
/// The result type for trying to encode something as nettext
|
||||
|
@ -96,40 +95,7 @@ pub fn raw(bytes: &[u8]) -> Result<'_> {
|
|||
Ok(Term(T::Str(bytes)))
|
||||
}
|
||||
|
||||
/// Term corresponding to a sequence of terms. Subsequences are banned and will raise an error.
|
||||
///
|
||||
/// ```
|
||||
/// use nettext::enc::*;
|
||||
///
|
||||
/// assert_eq!(seq([
|
||||
/// string("Hello").unwrap(),
|
||||
/// string("world").unwrap()
|
||||
/// ]).unwrap().encode(), b"Hello world");
|
||||
/// ```
|
||||
pub fn seq<'a, I: IntoIterator<Item = Term<'a>>>(terms: I) -> Result<'a> {
|
||||
let mut tmp = Vec::with_capacity(8);
|
||||
for t in terms {
|
||||
match t.0 {
|
||||
T::Seq(_) => return Err(Error::SeqInSeq),
|
||||
x => tmp.push(x),
|
||||
}
|
||||
}
|
||||
Ok(Term(T::Seq(tmp)))
|
||||
}
|
||||
|
||||
/// Term corresponding to a sequence of terms. Sub-sequences are flattenned.
|
||||
pub fn seq_flatten<'a, I: IntoIterator<Item = Term<'a>>>(terms: I) -> Term<'a> {
|
||||
let mut tmp = Vec::with_capacity(8);
|
||||
for t in terms {
|
||||
match t.0 {
|
||||
T::Seq(t) => tmp.extend(t),
|
||||
x => tmp.push(x),
|
||||
}
|
||||
}
|
||||
Term(T::Seq(tmp))
|
||||
}
|
||||
|
||||
/// Term corresponding to a list of terms.
|
||||
/// Term corresponding to a list of terms
|
||||
///
|
||||
/// ```
|
||||
/// use nettext::enc::*;
|
||||
|
@ -137,11 +103,41 @@ pub fn seq_flatten<'a, I: IntoIterator<Item = Term<'a>>>(terms: I) -> Term<'a> {
|
|||
/// assert_eq!(list([
|
||||
/// string("Hello").unwrap(),
|
||||
/// string("world").unwrap()
|
||||
/// ]).encode(), b"[\n Hello,\n world,\n]");
|
||||
/// ]).unwrap().encode(), b"Hello world");
|
||||
/// ```
|
||||
pub fn list<'a, I: IntoIterator<Item = Term<'a>>>(terms: I) -> Term<'a> {
|
||||
let terms = terms.into_iter().map(|x| x.0).collect::<Vec<_>>();
|
||||
Term(T::List(terms))
|
||||
pub fn list<'a, I: IntoIterator<Item = Term<'a>>>(terms: I) -> Result<'a> {
|
||||
let mut tmp = Vec::with_capacity(8);
|
||||
for t in terms {
|
||||
match t.0 {
|
||||
T::List(_) => return Err(Error::ListInList),
|
||||
x => tmp.push(x),
|
||||
}
|
||||
}
|
||||
Ok(Term(T::List(tmp)))
|
||||
}
|
||||
|
||||
/// Term corresponding to a list of terms. Sub-lists are flattenned.
|
||||
pub fn list_flatten<'a, I: IntoIterator<Item = Term<'a>>>(terms: I) -> Result<'a> {
|
||||
let mut tmp = Vec::with_capacity(8);
|
||||
for t in terms {
|
||||
match t.0 {
|
||||
T::List(t) => tmp.extend(t),
|
||||
x => tmp.push(x),
|
||||
}
|
||||
}
|
||||
Ok(Term(T::List(tmp)))
|
||||
}
|
||||
|
||||
/// Term corresponding to a list of terms. Sub-lists are represented as NESTED: `{.= sub list items }`.
|
||||
pub fn list_nested<'a, I: IntoIterator<Item = Term<'a>>>(terms: I) -> Result<'a> {
|
||||
let mut tmp = Vec::with_capacity(8);
|
||||
for t in terms {
|
||||
match t.0 {
|
||||
T::List(t) => tmp.push(Term(T::List(t)).nested().0),
|
||||
x => tmp.push(x),
|
||||
}
|
||||
}
|
||||
Ok(Term(T::List(tmp)))
|
||||
}
|
||||
|
||||
/// Term corresponding to a dictionnary of items
|
||||
|
@ -181,7 +177,7 @@ pub fn bytes(bytes: &[u8]) -> Term<'static> {
|
|||
}
|
||||
|
||||
/// Same as `bytes()`, but splits the byte slice in 48-byte chunks
|
||||
/// and encodes each chunk separately, putting them in a sequence of terms.
|
||||
/// and encodes each chunk separately, putting them in a list of terms.
|
||||
/// Usefull for long byte slices to have cleaner representations,
|
||||
/// mainly usefull for dictionnary keys.
|
||||
pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
||||
|
@ -190,7 +186,7 @@ pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
|||
.map(|b| T::OwnedStr(base64::encode_config(b, base64::URL_SAFE_NO_PAD).into_bytes()))
|
||||
.collect::<Vec<_>>();
|
||||
if chunks.len() > 1 {
|
||||
Term(T::Seq(chunks))
|
||||
Term(T::List(chunks))
|
||||
} else {
|
||||
Term(chunks.into_iter().next().unwrap_or(T::Str(b".")))
|
||||
}
|
||||
|
@ -198,15 +194,15 @@ pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
|||
|
||||
impl<'a> Term<'a> {
|
||||
/// Append a term to an existing term.
|
||||
/// Transforms the initial term into a seq if necessary.
|
||||
/// Transforms the initial term into a list if necessary.
|
||||
#[must_use]
|
||||
pub fn append(self, t: Term<'a>) -> Term<'a> {
|
||||
match self.0 {
|
||||
T::Seq(mut v) => {
|
||||
T::List(mut v) => {
|
||||
v.push(t.0);
|
||||
Term(T::Seq(v))
|
||||
Term(T::List(v))
|
||||
}
|
||||
x => Term(T::Seq(vec![x, t.0])),
|
||||
x => Term(T::List(vec![x, t.0])),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,6 +219,21 @@ impl<'a> Term<'a> {
|
|||
_ => Err(Error::NotADictionnary),
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a NESTED term of this term, by putting it in a dict
|
||||
/// with a single key `.`.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```
|
||||
/// use nettext::enc::*;
|
||||
///
|
||||
/// assert_eq!(list([string("hello").unwrap(), string("world").unwrap()]).unwrap().nested().encode(), b"{.= hello world }");
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn nested(self) -> Term<'a> {
|
||||
dict([(".", self)]).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// ---- additional internal functions for serde module ----
|
||||
|
@ -257,11 +268,6 @@ impl<'a> Term<'a> {
|
|||
self.0.encode_aux(&mut buf, 0, true);
|
||||
buf
|
||||
}
|
||||
|
||||
/// Generate the nettext representation of a term, as a String
|
||||
pub fn encode_string(self) -> String {
|
||||
unsafe { String::from_utf8_unchecked(self.encode()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> T<'a> {
|
||||
|
@ -274,9 +280,13 @@ impl<'a> T<'a> {
|
|||
buf.extend_from_slice(b"{}");
|
||||
} else if d.len() == 1 {
|
||||
let (k, v) = d.into_iter().next().unwrap();
|
||||
if k.as_ref() == b"." {
|
||||
buf.extend_from_slice(b"{.= ");
|
||||
} else {
|
||||
buf.extend_from_slice(b"{ ");
|
||||
buf.extend_from_slice(k.borrow());
|
||||
buf.extend_from_slice(b" = ");
|
||||
}
|
||||
v.encode_aux(buf, indent + 2, false);
|
||||
buf.extend_from_slice(b" }");
|
||||
} else {
|
||||
|
@ -301,29 +311,6 @@ impl<'a> T<'a> {
|
|||
}
|
||||
}
|
||||
T::List(l) => {
|
||||
if l.len() == 0 {
|
||||
buf.extend_from_slice(b"[]");
|
||||
} else if l.len() == 1 {
|
||||
buf.extend_from_slice(b"[ ");
|
||||
l.into_iter().next().unwrap().encode_aux(buf, indent + 2, false);
|
||||
buf.extend_from_slice(b" ]");
|
||||
} else {
|
||||
let indent2 = indent + 2;
|
||||
buf.extend_from_slice(b"[\n");
|
||||
for item in l {
|
||||
for _ in 0..indent2 {
|
||||
buf.push(b' ');
|
||||
}
|
||||
item.encode_aux(buf, indent2, false);
|
||||
buf.extend_from_slice(b",\n");
|
||||
}
|
||||
for _ in 0..indent {
|
||||
buf.push(b' ');
|
||||
}
|
||||
buf.push(b']');
|
||||
}
|
||||
}
|
||||
T::Seq(l) => {
|
||||
let indent2 = indent + 2;
|
||||
for (i, v) in l.into_iter().enumerate() {
|
||||
if !is_toplevel && buf.iter().rev().take_while(|c| **c != b'\n').count() >= 70 {
|
||||
|
@ -343,18 +330,13 @@ impl<'a> T<'a> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::debug;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn complex1() {
|
||||
let input = seq([
|
||||
let input = list([
|
||||
string("HELLO").unwrap(),
|
||||
string("alexhelloworld").unwrap(),
|
||||
list([
|
||||
string("dude").unwrap(),
|
||||
string("why").unwrap(),
|
||||
]),
|
||||
dict([
|
||||
("from", string("jxx").unwrap()),
|
||||
("subject", string("hello").unwrap()),
|
||||
|
@ -363,17 +345,37 @@ mod tests {
|
|||
.unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
let expected = "HELLO alexhelloworld [
|
||||
dude,
|
||||
why,
|
||||
] {
|
||||
let expected = b"HELLO alexhelloworld {
|
||||
data = { f1 = plop, f2 = kuko },
|
||||
from = jxx,
|
||||
subject = hello,
|
||||
}";
|
||||
let enc = input.encode();
|
||||
eprintln!("{}", debug(&enc));
|
||||
eprintln!("{}", expected);
|
||||
assert_eq!(debug(&enc), expected);
|
||||
eprintln!("{}", std::str::from_utf8(&enc).unwrap());
|
||||
eprintln!("{}", std::str::from_utf8(&expected[..]).unwrap());
|
||||
assert_eq!(&enc, &expected[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested() {
|
||||
assert!(list([
|
||||
string("a").unwrap(),
|
||||
string("b").unwrap(),
|
||||
list([string("c").unwrap(), string("d").unwrap()]).unwrap()
|
||||
])
|
||||
.is_err());
|
||||
|
||||
assert_eq!(
|
||||
list([
|
||||
string("a").unwrap(),
|
||||
string("b").unwrap(),
|
||||
list([string("c").unwrap(), string("d").unwrap()])
|
||||
.unwrap()
|
||||
.nested()
|
||||
])
|
||||
.unwrap()
|
||||
.encode(),
|
||||
b"a b {.= c d }"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -9,7 +9,7 @@
|
|||
//! let keypair = SigningKeyPair::gen_with_defaults();
|
||||
//!
|
||||
//! // Encode a fist object that represents a payload that will be hashed and signed
|
||||
//! let signed_payload = seq([
|
||||
//! let signed_payload = list([
|
||||
//! string("CALL").unwrap(),
|
||||
//! string("myfunction").unwrap(),
|
||||
//! dict([
|
||||
|
@ -44,7 +44,7 @@
|
|||
//!
|
||||
//! let object2 = decode(payload.raw()).unwrap();
|
||||
//!
|
||||
//! let [verb, arg1, arg2, pubkey] = object2.seq_of().unwrap();
|
||||
//! let [verb, arg1, arg2, pubkey] = object2.list_of().unwrap();
|
||||
//! let pubkey = pubkey.public_key().unwrap();
|
||||
//! assert!(verify_signature(&signature, payload.raw(), &pubkey));
|
||||
//!
|
||||
|
@ -100,9 +100,6 @@ pub(crate) const DICT_OPEN: u8 = b'{';
|
|||
pub(crate) const DICT_CLOSE: u8 = b'}';
|
||||
pub(crate) const DICT_ASSIGN: u8 = b'=';
|
||||
pub(crate) const DICT_DELIM: u8 = b',';
|
||||
pub(crate) const LIST_OPEN: u8 = b'[';
|
||||
pub(crate) const LIST_CLOSE: u8 = b']';
|
||||
pub(crate) const LIST_DELIM: u8 = b',';
|
||||
pub(crate) const STR_EXTRA_CHARS: &[u8] = b"._-+*?@:";
|
||||
|
||||
pub(crate) fn is_string_char(c: u8) -> bool {
|
||||
|
@ -112,7 +109,3 @@ pub(crate) fn is_string_char(c: u8) -> bool {
|
|||
pub(crate) fn is_whitespace(c: u8) -> bool {
|
||||
c.is_ascii_whitespace()
|
||||
}
|
||||
|
||||
pub(crate) fn debug(x: &[u8]) -> &str {
|
||||
std::str::from_utf8(x).unwrap_or("<invalid ascii>")
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use serde::de::{
|
|||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::debug as fmtdebug;
|
||||
use crate::dec::debug as fmtdebug;
|
||||
use crate::dec::*;
|
||||
use crate::serde::error::{Error, Result};
|
||||
|
||||
|
@ -15,8 +15,12 @@ pub struct Deserializer<'de, 'a>(Term<'de, 'a>);
|
|||
|
||||
impl<'de, 'a> Deserializer<'de, 'a> {
|
||||
fn from_term(input: &'a Term<'de, 'a>) -> Deserializer<'de, 'a> {
|
||||
if let Ok(nested) = input.nested() {
|
||||
Deserializer(nested)
|
||||
} else {
|
||||
Deserializer(Term(input.0.mkref()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize nettext from bytes
|
||||
|
@ -224,7 +228,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de, 'a> {
|
|||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
let [variant, args] = self.0.seq_of_first()?;
|
||||
let [variant, args] = self.0.list_of_first()?;
|
||||
if variant.string()? == name {
|
||||
visitor.visit_newtype_struct(&mut Deserializer(args))
|
||||
} else {
|
||||
|
@ -240,14 +244,14 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de, 'a> {
|
|||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_seq(&mut Seq(&self.0.list()?))
|
||||
visitor.visit_seq(&mut Seq(&self.0.list()))
|
||||
}
|
||||
|
||||
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_seq(&mut Seq(&self.0.seq()))
|
||||
visitor.visit_seq(&mut Seq(&self.0.list()))
|
||||
}
|
||||
|
||||
fn deserialize_tuple_struct<V>(
|
||||
|
@ -259,9 +263,9 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de, 'a> {
|
|||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
let [variant, args] = self.0.seq_of_first()?;
|
||||
let [variant, args] = self.0.list_of_first()?;
|
||||
if variant.string()? == name {
|
||||
visitor.visit_seq(&mut Seq(&args.seq()))
|
||||
visitor.visit_seq(&mut Seq(&args.list()))
|
||||
} else {
|
||||
Err(Error::custom(format!(
|
||||
"Expected {}, got: `{}`",
|
||||
|
@ -287,7 +291,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de, 'a> {
|
|||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
let [variant, data] = self.0.seq_of()?;
|
||||
let [variant, data] = self.0.list_of()?;
|
||||
if variant.string()? != name {
|
||||
return Err(Error::custom(format!(
|
||||
"Expected {}, got: `{}`",
|
||||
|
@ -410,7 +414,7 @@ impl<'de, 'a> EnumAccess<'de> for Enum<'de, 'a> {
|
|||
where
|
||||
V: DeserializeSeed<'de>,
|
||||
{
|
||||
let variant = &self.0.seq()[0];
|
||||
let variant = &self.0.list()[0];
|
||||
let variant = seed.deserialize(&mut Deserializer(Term(variant.0.mkref())))?;
|
||||
Ok((variant, self))
|
||||
}
|
||||
|
@ -420,7 +424,7 @@ impl<'de, 'a> VariantAccess<'de> for Enum<'de, 'a> {
|
|||
type Error = Error;
|
||||
|
||||
fn unit_variant(self) -> Result<()> {
|
||||
if self.0.seq().len() > 1 {
|
||||
if self.0.list().len() > 1 {
|
||||
Err(Error::custom("Spurrious data in unit variant"))
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -431,7 +435,7 @@ impl<'de, 'a> VariantAccess<'de> for Enum<'de, 'a> {
|
|||
where
|
||||
T: DeserializeSeed<'de>,
|
||||
{
|
||||
let [_, rest] = self.0.seq_of_first()?;
|
||||
let [_, rest] = self.0.list_of_first()?;
|
||||
seed.deserialize(&mut Deserializer(rest))
|
||||
}
|
||||
|
||||
|
@ -439,15 +443,15 @@ impl<'de, 'a> VariantAccess<'de> for Enum<'de, 'a> {
|
|||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
let [_, rest] = self.0.seq_of_first()?;
|
||||
visitor.visit_seq(&mut Seq(&rest.seq()))
|
||||
let [_, rest] = self.0.list_of_first()?;
|
||||
visitor.visit_seq(&mut Seq(&rest.list()))
|
||||
}
|
||||
|
||||
fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
let [_, rest] = self.0.seq_of_first()?;
|
||||
let [_, rest] = self.0.list_of_first()?;
|
||||
visitor.visit_map(&mut Dict::from_term(&rest)?)
|
||||
}
|
||||
}
|
||||
|
|
101
src/serde/mod.rs
101
src/serde/mod.rs
|
@ -13,18 +13,15 @@ mod tests {
|
|||
use super::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use crate::debug;
|
||||
|
||||
fn test_bidir<T: Serialize + for<'de> Deserialize<'de> + PartialEq + std::fmt::Debug>(
|
||||
input: T,
|
||||
expected: &str,
|
||||
expected: &[u8],
|
||||
) {
|
||||
eprintln!("Expecting: {}", expected);
|
||||
let ser = to_bytes(&input).unwrap();
|
||||
let ser = debug(&ser);
|
||||
eprintln!("Serialized: {}", ser);
|
||||
assert_eq!(ser, expected);
|
||||
assert_eq!(from_bytes::<T>(ser.as_bytes()).unwrap(), input);
|
||||
eprintln!("Serialized: {}", std::str::from_utf8(&ser).unwrap());
|
||||
assert_eq!(&ser, expected);
|
||||
assert_eq!(&from_bytes::<T>(&ser).unwrap(), &input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -39,12 +36,9 @@ mod tests {
|
|||
int: 1,
|
||||
seq: vec!["a".to_string(), "b".to_string()],
|
||||
};
|
||||
let expected = r#"Test {
|
||||
let expected = br#"Test {
|
||||
int = 1,
|
||||
seq = [
|
||||
YQ,
|
||||
Yg,
|
||||
],
|
||||
seq = YQ Yg,
|
||||
}"#;
|
||||
test_bidir(input, expected);
|
||||
|
||||
|
@ -58,22 +52,13 @@ mod tests {
|
|||
seq: vec!["c".to_string(), "d".to_string()],
|
||||
},
|
||||
];
|
||||
let expected = r#"[
|
||||
Test {
|
||||
let expected = br#"{.= Test {
|
||||
int = 1,
|
||||
seq = [
|
||||
YQ,
|
||||
Yg,
|
||||
],
|
||||
},
|
||||
Test {
|
||||
seq = YQ Yg,
|
||||
} } {.= Test {
|
||||
int = 2,
|
||||
seq = [
|
||||
Yw,
|
||||
ZA,
|
||||
],
|
||||
},
|
||||
]"#;
|
||||
seq = Yw ZA,
|
||||
} }"#;
|
||||
test_bidir(input, expected);
|
||||
}
|
||||
|
||||
|
@ -88,19 +73,19 @@ mod tests {
|
|||
}
|
||||
|
||||
let input = E::Unit;
|
||||
let expected = r#"E.Unit"#;
|
||||
let expected = br#"E.Unit"#;
|
||||
test_bidir(input, expected);
|
||||
|
||||
let input = E::Newtype(1);
|
||||
let expected = r#"E.Newtype 1"#;
|
||||
let expected = br#"E.Newtype 1"#;
|
||||
test_bidir(input, expected);
|
||||
|
||||
let input = E::Tuple(1, 2);
|
||||
let expected = r#"E.Tuple 1 2"#;
|
||||
let expected = br#"E.Tuple 1 2"#;
|
||||
test_bidir(input, expected);
|
||||
|
||||
let input = E::Struct { a: 1 };
|
||||
let expected = r#"E.Struct { a = 1 }"#;
|
||||
let expected = br#"E.Struct { a = 1 }"#;
|
||||
test_bidir(input, expected);
|
||||
|
||||
let input = vec![
|
||||
|
@ -112,45 +97,36 @@ mod tests {
|
|||
E::Tuple(3, 2),
|
||||
];
|
||||
let expected =
|
||||
r#"[
|
||||
E.Unit,
|
||||
E.Unit,
|
||||
E.Newtype 1,
|
||||
E.Tuple 1 2,
|
||||
E.Struct { a = 1 },
|
||||
E.Tuple 3 2,
|
||||
]"#;
|
||||
br#"E.Unit E.Unit {.= E.Newtype 1 } {.= E.Tuple 1 2 } {.= E.Struct { a = 1 } } {.=
|
||||
E.Tuple 3 2 }"#;
|
||||
test_bidir(input, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq1() {
|
||||
let input = (1, 2, 3, 4);
|
||||
let expected = r#"1 2 3 4"#;
|
||||
let expected = br#"1 2 3 4"#;
|
||||
test_bidir(input, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list() {
|
||||
let input = vec![1, 2, 3, 4];
|
||||
let expected = r#"[
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
]"#;
|
||||
fn test_seq2() {
|
||||
let input = (1, 2, (2, 3, 4), 5, 6);
|
||||
let expected = br#"1 2 {.= 2 3 4 } 5 6"#;
|
||||
test_bidir(input, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seqlist() {
|
||||
let input = vec![(1, 2), (2, 3), (3, 4), (5, 6)];
|
||||
let expected = r#"[
|
||||
1 2,
|
||||
2 3,
|
||||
3 4,
|
||||
5 6,
|
||||
]"#;
|
||||
fn test_seq3() {
|
||||
let input = [1, 2, 3, 4];
|
||||
let expected = br#"1 2 3 4"#;
|
||||
test_bidir(input, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq4() {
|
||||
let input = [[1, 2], [2, 3], [3, 4]];
|
||||
let expected = br#"{.= 1 2 } {.= 2 3 } {.= 3 4 }"#;
|
||||
test_bidir(input, expected);
|
||||
}
|
||||
|
||||
|
@ -159,7 +135,7 @@ mod tests {
|
|||
let mut input = HashMap::new();
|
||||
input.insert("hello".to_string(), "world".to_string());
|
||||
input.insert("dont".to_string(), "panic".to_string());
|
||||
let expected = r#"{
|
||||
let expected = br#"{
|
||||
ZG9udA = cGFuaWM,
|
||||
aGVsbG8 = d29ybGQ,
|
||||
}"#;
|
||||
|
@ -168,16 +144,9 @@ mod tests {
|
|||
let mut input = HashMap::new();
|
||||
input.insert(12, vec![42, 125]);
|
||||
input.insert(33, vec![19, 22, 21]);
|
||||
let expected = r#"{
|
||||
12 = [
|
||||
42,
|
||||
125,
|
||||
],
|
||||
33 = [
|
||||
19,
|
||||
22,
|
||||
21,
|
||||
],
|
||||
let expected = br#"{
|
||||
12 = 42 125,
|
||||
33 = 19 22 21,
|
||||
}"#;
|
||||
test_bidir(input, expected);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
|||
|
||||
type Error = Error;
|
||||
|
||||
type SerializeSeq = ListSerializer;
|
||||
type SerializeSeq = SeqSerializer;
|
||||
type SerializeTuple = SeqSerializer;
|
||||
type SerializeTupleStruct = SeqSerializer;
|
||||
type SerializeTupleVariant = SeqSerializer;
|
||||
|
@ -104,7 +104,7 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
|||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
Ok(value.serialize(self)?)
|
||||
Ok(value.serialize(self)?.nested())
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Self::Ok> {
|
||||
|
@ -128,7 +128,7 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
|||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
Ok(seq_flatten([string(name)?, value.serialize(self)?]))
|
||||
Ok(list_flatten([string(name)?, value.serialize(self)?])?)
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T>(
|
||||
|
@ -141,14 +141,14 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
|||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
Ok(seq_flatten([
|
||||
Ok(list_flatten([
|
||||
string_owned(format!("{}.{}", name, variant))?,
|
||||
value.serialize(self)?,
|
||||
]))
|
||||
])?)
|
||||
}
|
||||
|
||||
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||
Ok(ListSerializer { items: vec![] })
|
||||
Ok(SeqSerializer { items: vec![] })
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
|
||||
|
@ -213,6 +213,22 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
|||
pub struct SeqSerializer {
|
||||
items: Vec<Term<'static>>,
|
||||
}
|
||||
impl ser::SerializeSeq for SeqSerializer {
|
||||
type Ok = Term<'static>;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
self.items.push(value.serialize(&mut Serializer)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(list_nested(self.items.into_iter())?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::SerializeTuple for SeqSerializer {
|
||||
type Ok = Term<'static>;
|
||||
|
@ -227,7 +243,7 @@ impl ser::SerializeTuple for SeqSerializer {
|
|||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(seq(self.items.into_iter())?)
|
||||
Ok(list_nested(self.items.into_iter())?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,7 +260,7 @@ impl ser::SerializeTupleStruct for SeqSerializer {
|
|||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(seq(self.items.into_iter())?)
|
||||
Ok(list_nested(self.items.into_iter())?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,27 +277,7 @@ impl ser::SerializeTupleVariant for SeqSerializer {
|
|||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(seq(self.items.into_iter())?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ListSerializer {
|
||||
items: Vec<Term<'static>>,
|
||||
}
|
||||
impl ser::SerializeSeq for ListSerializer {
|
||||
type Ok = Term<'static>;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
self.items.push(value.serialize(&mut Serializer)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok> {
|
||||
Ok(list(self.items.into_iter()))
|
||||
Ok(list_nested(self.items.into_iter())?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,7 +334,7 @@ impl ser::SerializeStruct for StructSerializer {
|
|||
}
|
||||
|
||||
fn end(self) -> Result<Term<'static>> {
|
||||
Ok(seq([string(self.name)?, dict(self.fields.into_iter())?])?)
|
||||
Ok(list([string(self.name)?, dict(self.fields.into_iter())?])?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -361,7 +357,7 @@ impl ser::SerializeStructVariant for StructVariantSerializer {
|
|||
}
|
||||
|
||||
fn end(self) -> Result<Term<'static>> {
|
||||
Ok(seq([
|
||||
Ok(list([
|
||||
string_owned(format!("{}.{}", self.name, self.variant))?,
|
||||
dict(self.fields.into_iter())?,
|
||||
])?)
|
||||
|
|
Loading…
Reference in a new issue