2022-11-17 21:53:36 +00:00
|
|
|
//! Functions to decode nettext and helpers to map it to data structures
|
|
|
|
|
2022-11-17 10:48:43 +00:00
|
|
|
mod decode;
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2022-11-17 21:53:36 +00:00
|
|
|
use crate::crypto;
|
2022-11-17 10:48:43 +00:00
|
|
|
pub use decode::*;
|
|
|
|
|
2022-11-17 15:35:06 +00:00
|
|
|
/// A parsed nettext term, with many helpers for destructuring
|
2022-11-17 13:58:27 +00:00
|
|
|
///
|
|
|
|
/// Lifetime 'a is the lifetime of the buffer containing the encoded data.
|
|
|
|
///
|
|
|
|
/// Lifetime 'b is the lifetime of another Term from which this one is borrowed, when it
|
|
|
|
/// is returned by one of the helper functions, or 'static when first returned from
|
|
|
|
/// `decode()`
|
|
|
|
#[derive(Eq, PartialEq, Debug)]
|
|
|
|
pub struct Term<'a, 'b>(AnyTerm<'a, 'b>);
|
|
|
|
|
|
|
|
#[derive(Eq, PartialEq, Clone)]
|
|
|
|
pub(crate) enum AnyTerm<'a, 'b> {
|
2022-11-17 10:48:43 +00:00
|
|
|
Str(&'a [u8]),
|
2022-11-17 13:58:27 +00:00
|
|
|
Dict(&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
|
|
|
DictRef(&'a [u8], &'b HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
|
|
|
List(&'a [u8], Vec<NonListTerm<'a, 'b>>),
|
|
|
|
ListRef(&'a [u8], &'b [NonListTerm<'a, 'b>]),
|
2022-11-17 10:48:43 +00:00
|
|
|
}
|
|
|
|
|
2022-11-17 13:58:27 +00:00
|
|
|
#[derive(Eq, PartialEq, Clone)]
|
|
|
|
pub(crate) enum NonListTerm<'a, 'b> {
|
2022-11-17 10:48:43 +00:00
|
|
|
Str(&'a [u8]),
|
2022-11-17 13:58:27 +00:00
|
|
|
Dict(&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'b>>),
|
|
|
|
DictRef(&'a [u8], &'b HashMap<&'a [u8], 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 {
|
|
|
|
NonListTerm::Str(s) => AnyTerm::Str(s),
|
|
|
|
NonListTerm::Dict(raw, d) => AnyTerm::Dict(raw, d),
|
|
|
|
NonListTerm::DictRef(raw, d) => AnyTerm::DictRef(raw, d),
|
|
|
|
}
|
|
|
|
}
|
2022-11-17 10:48:43 +00:00
|
|
|
}
|
|
|
|
|
2022-11-17 13:58:27 +00:00
|
|
|
impl<'a, 'b> TryFrom<AnyTerm<'a, 'b>> for NonListTerm<'a, 'b> {
|
|
|
|
type Error = ();
|
|
|
|
fn try_from(x: AnyTerm<'a, 'b>) -> Result<NonListTerm<'a, 'b>, ()> {
|
2022-11-17 10:48:43 +00:00
|
|
|
match x {
|
2022-11-17 13:58:27 +00:00
|
|
|
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(()),
|
2022-11-17 10:48:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 13:58:27 +00:00
|
|
|
impl<'a> From<AnyTerm<'a, 'static>> for Term<'a, 'static> {
|
|
|
|
fn from(x: AnyTerm<'a, 'static>) -> Term<'a, 'static> {
|
|
|
|
Term(x)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---- PUBLIC IMPLS ----
|
|
|
|
|
2022-11-17 15:35:06 +00:00
|
|
|
/// The type of errors returned by helper functions on `Term`
|
2022-11-17 13:58:27 +00:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum TypeError {
|
2022-11-17 15:35:06 +00:00
|
|
|
/// The term could not be decoded in the given type
|
2022-11-17 13:58:27 +00:00
|
|
|
WrongType(&'static str),
|
2022-11-17 15:35:06 +00:00
|
|
|
/// The term is not an array of the requested length
|
2022-11-17 13:58:27 +00:00
|
|
|
WrongLength(usize, usize),
|
2022-11-17 15:35:06 +00:00
|
|
|
/// The dictionnary is missing a key
|
2022-11-17 13:58:27 +00:00
|
|
|
MissingKey(String),
|
2022-11-17 15:35:06 +00:00
|
|
|
/// The dictionnary contains an invalid key
|
2022-11-17 13:58:27 +00:00
|
|
|
UnexpectedKey(String),
|
2022-11-17 15:35:06 +00:00
|
|
|
/// The underlying raw string contains garbage (should not happen in theory)
|
|
|
|
Garbage,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<std::str::Utf8Error> for TypeError {
|
|
|
|
fn from(_x: std::str::Utf8Error) -> TypeError {
|
|
|
|
TypeError::Garbage
|
|
|
|
}
|
2022-11-17 13:58:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'b> Term<'a, 'b> {
|
2022-11-17 15:35:06 +00:00
|
|
|
// ---- STRUCTURAL MAPPINGS ----
|
|
|
|
|
2022-11-17 13:58:27 +00:00
|
|
|
/// Get the term's raw representation
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
|
|
|
/// let term = decode(b"hello world").unwrap();
|
|
|
|
/// assert_eq!(term.raw(), b"hello world");
|
|
|
|
/// ```
|
|
|
|
pub fn raw(&self) -> &'a [u8] {
|
|
|
|
self.0.raw()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// If the term is a single string, get that string
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
2022-11-17 15:35:06 +00:00
|
|
|
/// let term1 = decode(b"hello").unwrap();
|
|
|
|
/// assert_eq!(term1.str().unwrap(), "hello");
|
2022-11-17 13:58:27 +00:00
|
|
|
///
|
|
|
|
/// let term2 = decode(b"hello world").unwrap();
|
|
|
|
/// assert!(term2.str().is_err());
|
|
|
|
/// ```
|
2022-11-17 15:35:06 +00:00
|
|
|
pub fn str(&self) -> Result<&'a str, TypeError> {
|
2022-11-17 13:58:27 +00:00
|
|
|
match &self.0 {
|
2022-11-17 15:35:06 +00:00
|
|
|
AnyTerm::Str(s) => Ok(std::str::from_utf8(s)?),
|
2022-11-17 13:58:27 +00:00
|
|
|
_ => Err(TypeError::WrongType("STR")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// If the term is a single string, or a list containing only strings,
|
|
|
|
/// get its raw representation
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
2022-11-17 15:35:06 +00:00
|
|
|
/// let term1 = decode(b"hello world").unwrap();
|
|
|
|
/// assert_eq!(term1.string().unwrap(), "hello world");
|
2022-11-17 13:58:27 +00:00
|
|
|
///
|
|
|
|
/// let term2 = decode(b"hello { a= 5}").unwrap();
|
|
|
|
/// assert!(term2.string().is_err());
|
|
|
|
/// ```
|
2022-11-17 15:35:06 +00:00
|
|
|
pub fn string(&self) -> Result<&'a str, TypeError> {
|
2022-11-17 13:58:27 +00:00
|
|
|
match &self.0 {
|
2022-11-17 15:35:06 +00:00
|
|
|
AnyTerm::Str(s) => Ok(std::str::from_utf8(s)?),
|
|
|
|
AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => {
|
|
|
|
Ok(std::str::from_utf8(r)?)
|
|
|
|
}
|
2022-11-17 13:58:27 +00:00
|
|
|
_ => Err(TypeError::WrongType("STRING")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
2022-11-17 15:35:06 +00:00
|
|
|
/// let term1 = decode(b"hello").unwrap();
|
2022-11-17 13:58:27 +00:00
|
|
|
/// let list1 = term1.list();
|
|
|
|
/// assert_eq!(list1.len(), 1);
|
2022-11-17 15:35:06 +00:00
|
|
|
/// assert_eq!(list1[0].str().unwrap(), "hello");
|
2022-11-17 13:58:27 +00:00
|
|
|
///
|
2022-11-17 15:35:06 +00:00
|
|
|
/// let term2 = decode(b"hello world").unwrap();
|
2022-11-17 13:58:27 +00:00
|
|
|
/// let list2 = term2.list();
|
|
|
|
/// assert_eq!(list2.len(), 2);
|
2022-11-17 15:35:06 +00:00
|
|
|
/// assert_eq!(list2[0].str().unwrap(), "hello");
|
|
|
|
/// assert_eq!(list2[1].str().unwrap(), "world");
|
2022-11-17 13:58:27 +00:00
|
|
|
/// ```
|
|
|
|
pub fn list(&self) -> Vec<Term<'a, '_>> {
|
|
|
|
match self.0.mkref() {
|
|
|
|
AnyTerm::ListRef(_r, l) => l.iter().map(|x| Term(x.mkref().into())).collect::<Vec<_>>(),
|
|
|
|
x => vec![Term(x)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 15:35:06 +00:00
|
|
|
/// Same as `.list()`, but deconstructs it in a const length array,
|
|
|
|
/// dynamically checking if there are the correct number of items.
|
2022-11-17 13:58:27 +00:00
|
|
|
/// This allows to directly bind the resulting list into discrete variables.
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
2022-11-17 15:35:06 +00:00
|
|
|
/// let term1 = decode(b"hello").unwrap();
|
2022-11-17 13:58:27 +00:00
|
|
|
/// let [s1] = term1.list_of().unwrap();
|
2022-11-17 15:35:06 +00:00
|
|
|
/// assert_eq!(s1.str().unwrap(), "hello");
|
2022-11-17 13:58:27 +00:00
|
|
|
///
|
2022-11-17 15:35:06 +00:00
|
|
|
/// let term2 = decode(b"hello world").unwrap();
|
2022-11-17 13:58:27 +00:00
|
|
|
/// let [s2a, s2b] = term2.list_of().unwrap();
|
2022-11-17 15:35:06 +00:00
|
|
|
/// assert_eq!(s2a.str().unwrap(), "hello");
|
|
|
|
/// assert_eq!(s2b.str().unwrap(), "world");
|
2022-11-17 13:58:27 +00:00
|
|
|
/// ```
|
|
|
|
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 `.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 list term
|
|
|
|
/// that is returned as the Nth return variable.
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
2022-11-17 15:35:06 +00:00
|
|
|
/// let term1 = decode(b"hello world").unwrap();
|
2022-11-17 13:58:27 +00:00
|
|
|
/// let [s1a, s1b] = term1.list_of_first().unwrap();
|
2022-11-17 15:35:06 +00:00
|
|
|
/// assert_eq!(s1a.str().unwrap(), "hello");
|
|
|
|
/// assert_eq!(s1b.str().unwrap(), "world");
|
2022-11-17 13:58:27 +00:00
|
|
|
///
|
2022-11-17 15:35:06 +00:00
|
|
|
/// let term2 = decode(b"hello mighty world").unwrap();
|
2022-11-17 13:58:27 +00:00
|
|
|
/// let [s2a, s2b] = term2.list_of_first().unwrap();
|
2022-11-17 15:35:06 +00:00
|
|
|
/// assert_eq!(s2a.str().unwrap(), "hello");
|
2022-11-17 13:58:27 +00:00
|
|
|
/// assert_eq!(s2b.list().len(), 2);
|
|
|
|
/// assert_eq!(s2b.raw(), b"mighty world");
|
|
|
|
/// ```
|
|
|
|
pub fn list_of_first<const N: usize>(&self) -> Result<[Term<'a, '_>; N], TypeError> {
|
|
|
|
match self.0.mkref() {
|
2022-11-17 14:02:33 +00:00
|
|
|
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<_>>()
|
|
|
|
.try_into()
|
|
|
|
.unwrap()),
|
|
|
|
std::cmp::Ordering::Greater => {
|
2022-11-17 13:58:27 +00:00
|
|
|
let mut ret = Vec::with_capacity(N);
|
|
|
|
for item in list[0..N - 1].iter() {
|
|
|
|
ret.push(Term(item.mkref().into()));
|
|
|
|
}
|
|
|
|
|
|
|
|
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..];
|
|
|
|
|
2022-11-17 14:10:14 +00:00
|
|
|
ret.push(Term(AnyTerm::ListRef(remaining_raw, &list[N - 1..])));
|
2022-11-17 13:58:27 +00:00
|
|
|
|
|
|
|
Ok(ret.try_into().unwrap())
|
|
|
|
}
|
2022-11-17 14:02:33 +00:00
|
|
|
},
|
2022-11-17 13:58:27 +00:00
|
|
|
x if N == 1 => Ok([Term(x)]
|
|
|
|
.into_iter()
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.try_into()
|
|
|
|
.unwrap()),
|
|
|
|
_ => Err(TypeError::WrongLength(1, N)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 15:35:06 +00:00
|
|
|
/// Checks term is a dictionnary and returns hashmap of inner terms.
|
2022-11-17 13:58:27 +00:00
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
|
|
|
/// let term = decode(b"{ k1 = v1, k2 = v2 }").unwrap();
|
|
|
|
/// let dict = term.dict().unwrap();
|
2022-11-17 15:35:06 +00:00
|
|
|
/// assert_eq!(dict.get("k1").unwrap().str().unwrap(), "v1");
|
|
|
|
/// assert_eq!(dict.get("k2").unwrap().str().unwrap(), "v2");
|
2022-11-17 13:58:27 +00:00
|
|
|
/// ```
|
2022-11-17 15:35:06 +00:00
|
|
|
pub fn dict(&self) -> Result<HashMap<&'a str, Term<'a, '_>>, TypeError> {
|
2022-11-17 13:58:27 +00:00
|
|
|
match self.0.mkref() {
|
2022-11-17 15:35:06 +00:00
|
|
|
AnyTerm::DictRef(_, d) => {
|
|
|
|
let mut res = HashMap::with_capacity(d.len());
|
|
|
|
for (k, t) in d.iter() {
|
|
|
|
res.insert(std::str::from_utf8(k)?, Term(t.mkref()));
|
|
|
|
}
|
|
|
|
Ok(res)
|
|
|
|
}
|
2022-11-17 13:58:27 +00:00
|
|
|
_ => Err(TypeError::WrongType("DICT")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Checks term is a dictionnary whose keys are exactly those supplied,
|
|
|
|
/// and returns the associated values as a list.
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
2022-11-17 14:06:42 +00:00
|
|
|
/// let term = decode(b"{ k1 = v1, k2 = v2, k3 = v3 }").unwrap();
|
2022-11-17 15:35:06 +00:00
|
|
|
/// let [s1, s2] = term.dict_of(["k1", "k2"], true).unwrap();
|
|
|
|
/// assert_eq!(s1.str().unwrap(), "v1");
|
|
|
|
/// assert_eq!(s2.str().unwrap(), "v2");
|
2022-11-17 14:06:42 +00:00
|
|
|
///
|
2022-11-17 15:56:55 +00:00
|
|
|
/// assert!(term.dict_of(["k1", "k2"], false).is_err());
|
2022-11-17 13:58:27 +00:00
|
|
|
/// ```
|
2022-11-17 15:35:06 +00:00
|
|
|
pub fn dict_of<const N: usize, T: AsRef<[u8]>>(
|
2022-11-17 13:58:27 +00:00
|
|
|
&self,
|
2022-11-17 15:35:06 +00:00
|
|
|
keys: [T; N],
|
2022-11-17 14:06:42 +00:00
|
|
|
allow_extra_keys: bool,
|
2022-11-17 13:58:27 +00:00
|
|
|
) -> Result<[Term<'a, '_>; N], TypeError> {
|
|
|
|
match self.0.mkref() {
|
|
|
|
AnyTerm::DictRef(_, dict) => {
|
|
|
|
// Check all required keys exist in dictionnary
|
|
|
|
for k in keys.iter() {
|
2022-11-17 15:35:06 +00:00
|
|
|
if !dict.contains_key(k.as_ref()) {
|
|
|
|
return Err(TypeError::MissingKey(debug(k.as_ref()).to_string()));
|
2022-11-17 13:58:27 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-17 14:06:42 +00:00
|
|
|
if !allow_extra_keys {
|
|
|
|
// Check that dictionnary contains no extraneous keys
|
|
|
|
for k in dict.keys() {
|
2022-11-17 15:35:06 +00:00
|
|
|
if !keys.iter().any(|k2| k2.as_ref() == *k) {
|
2022-11-17 14:06:42 +00:00
|
|
|
return Err(TypeError::UnexpectedKey(debug(k).to_string()));
|
|
|
|
}
|
2022-11-17 13:58:27 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-17 15:35:06 +00:00
|
|
|
Ok(keys.map(|k| Term(dict.get(k.as_ref()).unwrap().mkref())))
|
2022-11-17 13:58:27 +00:00
|
|
|
}
|
|
|
|
_ => Err(TypeError::WrongType("DICT")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Checks term is a dictionnary whose keys are included in those supplied,
|
|
|
|
/// and returns the associated values as a list of options.
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
2022-11-17 14:06:42 +00:00
|
|
|
/// let term = decode(b"{ k1 = v1, k2 = v2, k4 = v4 }").unwrap();
|
2022-11-17 15:56:55 +00:00
|
|
|
/// let [s1, s2, s3] = term.dict_of_opt(["k1", "k2", "k3"], true).unwrap();
|
2022-11-17 15:35:06 +00:00
|
|
|
/// assert_eq!(s1.unwrap().str().unwrap(), "v1");
|
|
|
|
/// assert_eq!(s2.unwrap().str().unwrap(), "v2");
|
2022-11-17 13:58:27 +00:00
|
|
|
/// assert!(s3.is_none());
|
2022-11-17 14:06:42 +00:00
|
|
|
///
|
2022-11-17 15:35:06 +00:00
|
|
|
/// assert!(term.dict_of_opt(["k1", "k2", "k3"], false).is_err());
|
2022-11-17 13:58:27 +00:00
|
|
|
/// ```
|
2022-11-17 15:35:06 +00:00
|
|
|
pub fn dict_of_opt<const N: usize, T: AsRef<[u8]>>(
|
2022-11-17 13:58:27 +00:00
|
|
|
&self,
|
2022-11-17 15:35:06 +00:00
|
|
|
keys: [T; N],
|
2022-11-17 14:06:42 +00:00
|
|
|
allow_extra_keys: bool,
|
2022-11-17 13:58:27 +00:00
|
|
|
) -> Result<[Option<Term<'a, '_>>; N], TypeError> {
|
|
|
|
match self.0.mkref() {
|
|
|
|
AnyTerm::DictRef(_, dict) => {
|
2022-11-17 14:06:42 +00:00
|
|
|
if !allow_extra_keys {
|
|
|
|
// Check that dictionnary contains no extraneous keys
|
|
|
|
for k in dict.keys() {
|
2022-11-17 15:35:06 +00:00
|
|
|
if !keys.iter().any(|x| x.as_ref() == *k) {
|
2022-11-17 14:06:42 +00:00
|
|
|
return Err(TypeError::UnexpectedKey(debug(k).to_string()));
|
|
|
|
}
|
2022-11-17 13:58:27 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-17 15:35:06 +00:00
|
|
|
Ok(keys.map(|k| dict.get(k.as_ref()).map(|x| Term(x.mkref()))))
|
2022-11-17 13:58:27 +00:00
|
|
|
}
|
|
|
|
_ => Err(TypeError::WrongType("DICT")),
|
|
|
|
}
|
|
|
|
}
|
2022-11-17 15:35:06 +00:00
|
|
|
|
|
|
|
// ---- TYPE CASTS ----
|
|
|
|
|
|
|
|
/// Try to interpret this str as an i64
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
|
|
|
/// let term = decode(b"42").unwrap();
|
|
|
|
/// assert_eq!(term.int().unwrap(), 42);
|
|
|
|
/// ```
|
|
|
|
pub fn int(&self) -> Result<i64, TypeError> {
|
|
|
|
self.str()?
|
|
|
|
.parse::<i64>()
|
|
|
|
.map_err(|_| TypeError::WrongType("INT"))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to interpret this string as base64-encoded bytes (uses URL-safe, no-padding encoding)
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
|
|
|
/// let term = decode(b"aGVsbG8sIHdvcmxkIQ").unwrap();
|
|
|
|
/// assert_eq!(term.bytes().unwrap(), b"hello, world!");
|
|
|
|
/// ```
|
|
|
|
pub fn bytes(&self) -> Result<Vec<u8>, TypeError> {
|
|
|
|
let encoded = match &self.0 {
|
|
|
|
AnyTerm::Str(s) => s,
|
|
|
|
AnyTerm::List(r, l) if l.iter().all(|x| matches!(x, NonListTerm::Str(_))) => r,
|
|
|
|
_ => return Err(TypeError::WrongType("BYTES")),
|
|
|
|
};
|
|
|
|
base64::decode_config(encoded, base64::URL_SAFE_NO_PAD)
|
|
|
|
.map_err(|_| TypeError::WrongType("BYTES"))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to interpret this string as base64-encoded bytes,
|
|
|
|
/// with an exact length.
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
|
|
|
/// let term = decode(b"aGVsbG8sIHdvcmxkIQ").unwrap();
|
|
|
|
/// assert_eq!(&term.bytes_exact::<13>().unwrap(), b"hello, world!");
|
|
|
|
/// ```
|
|
|
|
pub fn bytes_exact<const N: usize>(&self) -> Result<[u8; N], TypeError> {
|
|
|
|
let bytes = self.bytes()?;
|
|
|
|
let bytes_len = bytes.len();
|
|
|
|
bytes
|
|
|
|
.try_into()
|
|
|
|
.map_err(|_| TypeError::WrongLength(bytes_len, N))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---- CRYPTO HELPERS ----
|
|
|
|
|
|
|
|
#[cfg(feature = "blake2")]
|
|
|
|
impl<'a, 'b> Term<'a, 'b> {
|
|
|
|
/// Try to interpret this string as a Blake2b512 digest (32-bytes base64 encoded)
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use nettext::dec::decode;
|
|
|
|
///
|
|
|
|
/// let term = decode(b"{
|
|
|
|
/// message = hello,
|
|
|
|
/// hash = 5M-jmj03vjHFlgnoB5cHmcqmihm_qhUTXxZQheAdQaZboeGxRq62vQCStJ6sIUwQPM-jo2WVS7vlL3Sis2IMlA
|
|
|
|
/// }").unwrap();
|
|
|
|
/// let [msg, hash] = term.dict_of(["message", "hash"], false).unwrap();
|
2022-11-17 22:58:44 +00:00
|
|
|
/// assert!(hash.b2sum().unwrap().verify(msg.raw()).is_ok());
|
2022-11-17 15:35:06 +00:00
|
|
|
/// ```
|
2022-11-17 21:53:36 +00:00
|
|
|
pub fn b2sum(&self) -> Result<crypto::Blake2Sum, TypeError> {
|
|
|
|
Ok(crypto::Blake2Sum::from_bytes(self.bytes_exact()?))
|
2022-11-17 15:35:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "ed25519-dalek")]
|
|
|
|
impl<'a, 'b> Term<'a, 'b> {
|
|
|
|
/// Try to interpret this string as an ed25519 keypair (64 bytes base64 encoded)
|
2022-11-17 21:53:36 +00:00
|
|
|
pub fn keypair(&self) -> Result<crypto::Keypair, TypeError> {
|
2022-11-17 15:35:06 +00:00
|
|
|
let bytes = self.bytes_exact::<64>()?;
|
2022-11-17 21:53:36 +00:00
|
|
|
crypto::Keypair::from_bytes(&bytes).map_err(|_| TypeError::WrongType("KEYPAIR"))
|
2022-11-17 15:35:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to interpret this string as an ed25519 public key (32 bytes base64 encoded)
|
2022-11-17 21:53:36 +00:00
|
|
|
pub fn public_key(&self) -> Result<crypto::PublicKey, TypeError> {
|
2022-11-17 15:35:06 +00:00
|
|
|
let bytes = self.bytes_exact::<32>()?;
|
2022-11-17 21:53:36 +00:00
|
|
|
crypto::PublicKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("PUBLICKEY"))
|
2022-11-17 15:35:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to interpret this string as an ed25519 secret key (32 bytes base64 encoded)
|
2022-11-17 21:53:36 +00:00
|
|
|
pub fn secret_key(&self) -> Result<crypto::SecretKey, TypeError> {
|
2022-11-17 15:35:06 +00:00
|
|
|
let bytes = self.bytes_exact::<32>()?;
|
2022-11-17 21:53:36 +00:00
|
|
|
crypto::SecretKey::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SECRETKEY"))
|
2022-11-17 15:35:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to interpret this string as an ed25519 signature (64 bytes base64 encoded)
|
2022-11-17 21:53:36 +00:00
|
|
|
pub fn signature(&self) -> Result<crypto::Signature, TypeError> {
|
2022-11-17 15:35:06 +00:00
|
|
|
let bytes = self.bytes_exact::<64>()?;
|
2022-11-17 21:53:36 +00:00
|
|
|
crypto::Signature::from_bytes(&bytes).map_err(|_| TypeError::WrongType("SIGNATURE"))
|
2022-11-17 15:35:06 +00:00
|
|
|
}
|
2022-11-17 13:58:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ---- INTERNAL IMPLS ----
|
|
|
|
|
|
|
|
impl<'a, 'b> AnyTerm<'a, 'b> {
|
|
|
|
fn raw(&self) -> &'a [u8] {
|
|
|
|
match self {
|
|
|
|
AnyTerm::Str(s) => s,
|
|
|
|
AnyTerm::Dict(r, _)
|
|
|
|
| AnyTerm::DictRef(r, _)
|
|
|
|
| AnyTerm::List(r, _)
|
|
|
|
| AnyTerm::ListRef(r, _) => r,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn mkref(&self) -> AnyTerm<'a, '_> {
|
|
|
|
match &self {
|
|
|
|
AnyTerm::Str(s) => AnyTerm::Str(s),
|
2022-11-17 14:02:33 +00:00
|
|
|
AnyTerm::Dict(r, d) => AnyTerm::DictRef(r, d),
|
2022-11-17 13:58:27 +00:00
|
|
|
AnyTerm::DictRef(r, d) => AnyTerm::DictRef(r, d),
|
|
|
|
AnyTerm::List(r, l) => AnyTerm::ListRef(r, &l[..]),
|
|
|
|
AnyTerm::ListRef(r, l) => AnyTerm::ListRef(r, l),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'b> NonListTerm<'a, 'b> {
|
|
|
|
fn raw(&self) -> &'a [u8] {
|
|
|
|
match &self {
|
|
|
|
NonListTerm::Str(s) => s,
|
|
|
|
NonListTerm::Dict(r, _) | NonListTerm::DictRef(r, _) => r,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn mkref(&self) -> NonListTerm<'a, '_> {
|
|
|
|
match &self {
|
|
|
|
NonListTerm::Str(s) => NonListTerm::Str(s),
|
2022-11-17 14:02:33 +00:00
|
|
|
NonListTerm::Dict(r, d) => NonListTerm::DictRef(r, d),
|
2022-11-17 13:58:27 +00:00
|
|
|
NonListTerm::DictRef(r, d) => NonListTerm::DictRef(r, d),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 15:35:06 +00:00
|
|
|
// ---- DISPLAY REPR = Raw nettext representation ----
|
2022-11-17 13:58:27 +00:00
|
|
|
|
|
|
|
impl<'a, 'b> std::fmt::Display for AnyTerm<'a, 'b> {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"{}",
|
|
|
|
std::str::from_utf8(self.raw()).map_err(|_| Default::default())?
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'b> std::fmt::Display for Term<'a, 'b> {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
|
|
write!(f, "{}", self.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---- DEBUG REPR ----
|
2022-11-17 10:48:43 +00:00
|
|
|
|
2022-11-17 15:35:06 +00:00
|
|
|
pub(crate) fn debug(x: &[u8]) -> &str {
|
2022-11-17 10:48:43 +00:00
|
|
|
std::str::from_utf8(x).unwrap_or("<invalid ascii>")
|
|
|
|
}
|
|
|
|
|
2022-11-17 13:58:27 +00:00
|
|
|
impl<'a, 'b> std::fmt::Debug for AnyTerm<'a, 'b> {
|
2022-11-17 10:48:43 +00:00
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
2022-11-17 13:58:27 +00:00
|
|
|
match self.mkref() {
|
|
|
|
AnyTerm::Str(s) => write!(f, "Str(`{}`)", debug(s)),
|
|
|
|
AnyTerm::DictRef(raw, d) => {
|
2022-11-17 10:48:43 +00:00
|
|
|
write!(f, "Dict<`{}`", debug(raw))?;
|
|
|
|
for (k, v) in d.iter() {
|
|
|
|
write!(f, "\n `{}`={:?}", debug(k), v)?;
|
|
|
|
}
|
|
|
|
write!(f, ">")
|
|
|
|
}
|
2022-11-17 13:58:27 +00:00
|
|
|
AnyTerm::ListRef(raw, l) => {
|
2022-11-17 10:48:43 +00:00
|
|
|
write!(f, "List[`{}`", debug(raw))?;
|
|
|
|
for i in l.iter() {
|
|
|
|
write!(f, "\n {:?}", i)?;
|
|
|
|
}
|
|
|
|
write!(f, "]")
|
|
|
|
}
|
2022-11-17 13:58:27 +00:00
|
|
|
_ => unreachable!(),
|
2022-11-17 10:48:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 13:58:27 +00:00
|
|
|
impl<'a, 'b> std::fmt::Debug for NonListTerm<'a, 'b> {
|
2022-11-17 10:48:43 +00:00
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
2022-11-17 13:58:27 +00:00
|
|
|
match self.mkref() {
|
2022-11-17 10:48:43 +00:00
|
|
|
NonListTerm::Str(s) => write!(f, "Str(`{}`)", debug(s)),
|
2022-11-17 13:58:27 +00:00
|
|
|
NonListTerm::DictRef(raw, d) => {
|
2022-11-17 10:48:43 +00:00
|
|
|
write!(f, "Dict<`{}`", debug(raw))?;
|
|
|
|
for (k, v) in d.iter() {
|
|
|
|
write!(f, "\n `{}`={:?}", debug(k), v)?;
|
|
|
|
}
|
|
|
|
write!(f, ">")
|
|
|
|
}
|
2022-11-17 13:58:27 +00:00
|
|
|
_ => unreachable!(),
|
2022-11-17 10:48:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|