nettext 0.4.1

A text-based data format for cryptographic network protocols
Documentation
use std::collections::HashMap;

use nom::{
    branch::alt,
    bytes::complete::{tag, take_while, take_while1},
    combinator::{map, opt},
    multi::{separated_list0, separated_list1},
    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,
};

// ----

/// Decodes a nettext string into the term it represents.
pub fn decode(input: &[u8]) -> std::result::Result<Term<'_, '_>, DecodeError<'_>> {
    let (rest, term) = decode_term(input)?;
    let (end, _) = take_while(is_whitespace)(rest)?;
    if !end.is_empty() {
        return Err(DecodeError::Garbage(end));
    }
    Ok(Term(term))
}

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)?;

    if seq.len() == 1 {
        Ok((rest, seq.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)))
    }
}

fn decode_nonseq_term(input: &[u8]) -> IResult<&'_ [u8], NonSeqTerm<'_, '_>> {
    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)),
    ))(input)?;
    Ok((rest, term))
}

fn decode_str(input: &[u8]) -> IResult<&'_ [u8], &'_ [u8]> {
    let (rest, data) = take_while1(is_string_char)(input)?;
    Ok((rest, data))
}

type DictType<'a> = (&'a [u8], HashMap<&'a [u8], AnyTerm<'a, 'a>>);

fn decode_dict(dict_begin: &[u8]) -> IResult<&'_ [u8], DictType<'_>> {
    let (d, _) = tag(&[DICT_OPEN][..])(dict_begin)?;
    let (d, items) = separated_list0(dict_separator, decode_dict_item)(d)?;
    let (d, _) = opt(dict_separator)(d)?;
    let (d, _) = take_while(is_whitespace)(d)?;
    let (dict_end, _) = tag(&[DICT_CLOSE][..])(d)?;

    let dict = items.into_iter().collect::<HashMap<_, _>>();

    let raw_len = dict_begin.input_len() - dict_end.input_len();
    let dict_raw = &dict_begin[..raw_len];

    Ok((dict_end, (dict_raw, dict)))
}

fn dict_separator(d: &[u8]) -> IResult<&'_ [u8], ()> {
    let (d, _) = take_while(is_whitespace)(d)?;
    let (d, _) = tag(&[DICT_DELIM][..])(d)?;
    Ok((d, ()))
}

fn decode_dict_item(d: &[u8]) -> IResult<&'_ [u8], (&'_ [u8], AnyTerm<'_, '_>)> {
    let (d, _) = take_while(is_whitespace)(d)?;
    let (d, key) = decode_str(d)?;
    let (d, _) = take_while(is_whitespace)(d)?;
    let (d, _) = tag(&[DICT_ASSIGN][..])(d)?;
    let (d, value) = decode_term(d)?;
    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)]
mod tests {
    use super::*;

    #[test]
    fn simple_str() {
        let bytes = b" plop ";
        assert_eq!(decode(bytes), Ok(AnyTerm::Str(b"plop").into()));
    }

    #[test]
    fn seq_of_str_str() {
        let bytes = b" plop plap  plip   ploup   ";
        assert_eq!(
            decode(bytes),
            Ok(AnyTerm::Seq(
                b"plop plap  plip   ploup",
                vec![
                    NonSeqTerm::Str(b"plop"),
                    NonSeqTerm::Str(b"plap"),
                    NonSeqTerm::Str(b"plip"),
                    NonSeqTerm::Str(b"ploup"),
                ]
            )
            .into())
        );
    }

    #[test]
    fn simple_dict() {
        let bytes = b" { aze = hello; by = bojzkz  pipo;    ccde = ke } ";
        assert_eq!(
            decode(bytes),
            Ok(AnyTerm::Dict(
                b"{ aze = hello; by = bojzkz  pipo;    ccde = ke }",
                [
                    (&b"aze"[..], AnyTerm::Str(b"hello")),
                    (
                        &b"by"[..],
                        AnyTerm::Seq(
                            b"bojzkz  pipo",
                            vec![NonSeqTerm::Str(b"bojzkz"), NonSeqTerm::Str(b"pipo")]
                        )
                    ),
                    (&b"ccde"[..], AnyTerm::Str(b"ke")),
                ]
                .into_iter()
                .collect()
            )
            .into())
        );
    }

    #[test]
    fn simple_dict_2() {
        let bytes = b" { aze = hello; by = bojzkz  pipo  ;    ccde = ke  ; } ";
        assert_eq!(
            decode(bytes),
            Ok(AnyTerm::Dict(
                b"{ aze = hello; by = bojzkz  pipo  ;    ccde = ke  ; }",
                [
                    (&b"aze"[..], AnyTerm::Str(b"hello")),
                    (
                        &b"by"[..],
                        AnyTerm::Seq(
                            b"bojzkz  pipo",
                            vec![NonSeqTerm::Str(b"bojzkz"), NonSeqTerm::Str(b"pipo")]
                        )
                    ),
                    (&b"ccde"[..], AnyTerm::Str(b"ke")),
                ]
                .into_iter()
                .collect()
            )
            .into())
        );
    }

    #[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(
                b"HEAD alexpubkey",
                vec![NonSeqTerm::Str(b"HEAD"), NonSeqTerm::Str(b"alexpubkey")]
            )
            .into()),
        );
    }

    #[test]
    fn real_world_2() {
        let bytes = b"STANCE sthash stsign { author = alexpubkey; height = 12; parent = parenthash; data = MESSAGE { text = hello } }";
        assert_eq!(
            decode(bytes),
            Ok(AnyTerm::Seq(
				&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 } }",
						[
							(&b"author"[..], AnyTerm::Str(b"alexpubkey")),
							(&b"height"[..], AnyTerm::Str(b"12")),
							(&b"parent"[..], AnyTerm::Str(b"parenthash")),
							(&b"data"[..], AnyTerm::Seq(
									b"MESSAGE { text = hello }",
									vec![
										NonSeqTerm::Str(b"MESSAGE"),
										NonSeqTerm::Dict(
											b"{ text = hello }",
											[
												(&b"text"[..], AnyTerm::Str(b"hello")),
											]
											.into_iter()
											.collect()
										)
									]
							))
					].into_iter().collect()
				),
			]).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())
        );
    }
}