webwire-cli 0.1.6

Contract-First API System - Command Line Interface
Documentation
#[cfg(test)]
use crate::idl::common::assert_parse;
use crate::idl::common::{parse_field_separator, parse_identifier, trailing_comma, ws, Span};
use nom::{
    branch::alt,
    bytes::complete::tag,
    character::complete::char,
    combinator::{cut, map, opt},
    error::context,
    multi::{many0, separated_list0},
    sequence::{preceded, separated_pair, terminated, tuple},
    IResult,
};

#[derive(Debug, PartialEq)]
pub enum Type {
    Ref(TypeRef),
    Array(Box<Type>),
    Map(Box<Type>, Box<Type>),
}

#[derive(Debug, PartialEq)]
pub struct TypeRef {
    pub abs: bool,
    pub ns: Vec<String>,
    pub name: String,
    pub generics: Vec<Type>,
}

pub fn parse_none(input: Span) -> IResult<Span, Option<Type>> {
    map(tag("None"), |_| None)(input)
}

pub fn parse_type_ref(input: Span) -> IResult<Span, TypeRef> {
    map(
        tuple((
            map(opt(tag("::")), |r| r.is_some()),
            parse_identifier,
            many0(preceded(tag("::"), parse_identifier)),
            parse_generics,
        )),
        |(abs, path_first, mut path, generics)| {
            let (ns, name): (Vec<String>, String) = match path.pop() {
                Some(name) => {
                    path.insert(0, path_first);
                    (path, name)
                }
                None => (path, path_first),
            };
            TypeRef {
                abs,
                ns,
                name,
                generics,
            }
        },
    )(input)
}

fn parse_generics(input: Span) -> IResult<Span, Vec<Type>> {
    map(
        opt(preceded(
            preceded(ws, char('<')),
            cut(terminated(
                separated_list0(parse_field_separator, preceded(ws, parse_type)),
                preceded(trailing_comma, preceded(ws, char('>'))),
            )),
        )),
        |v| match v {
            Some(v) => v,
            None => Vec::with_capacity(0),
        },
    )(input)
}

fn parse_type_array(input: Span) -> IResult<Span, Type> {
    context(
        "array",
        preceded(
            char('['),
            cut(terminated(
                preceded(ws, map(parse_type, |t| Type::Array(Box::new(t)))),
                preceded(ws, char(']')),
            )),
        ),
    )(input)
}

fn parse_type_map_inner(input: Span) -> IResult<Span, Type> {
    map(
        separated_pair(
            preceded(ws, parse_type),
            cut(preceded(ws, char(':'))),
            preceded(ws, parse_type),
        ),
        |t| Type::Map(Box::new(t.0), Box::new(t.1)),
    )(input)
}

fn parse_type_map(input: Span) -> IResult<Span, Type> {
    context(
        "map",
        preceded(
            char('{'),
            cut(terminated(
                preceded(ws, parse_type_map_inner),
                preceded(ws, char('}')),
            )),
        ),
    )(input)
}

pub fn parse_opt_type(input: Span) -> IResult<Span, Option<Type>> {
    preceded(ws, alt((parse_none, map(parse_type, Some))))(input)
}

pub fn parse_type(input: Span) -> IResult<Span, Type> {
    preceded(
        ws,
        alt((
            map(parse_type_ref, Type::Ref),
            parse_type_array,
            parse_type_map,
        )),
    )(input)
}

#[test]
fn test_parse_none() {
    assert_parse(parse_opt_type(Span::new("None")), None);
}

#[test]
fn test_parse_type_ref_rel_without_ns() {
    assert_parse(
        parse_type(Span::new("T")),
        Type::Ref(TypeRef {
            abs: false,
            ns: vec![],
            name: "T".to_string(),
            generics: vec![],
        }),
    );
}

#[test]
fn test_parse_type_ref_abs_without_ns() {
    assert_parse(
        parse_type(Span::new("::T")),
        Type::Ref(TypeRef {
            abs: true,
            ns: vec![],
            name: "T".to_string(),
            generics: vec![],
        }),
    );
}

#[test]
fn test_parse_type_ref_rel_with_ns() {
    assert_parse(
        parse_type(Span::new("ns::T")),
        Type::Ref(TypeRef {
            abs: false,
            ns: vec!["ns".to_string()],
            name: "T".to_string(),
            generics: vec![],
        }),
    );
}

#[test]
fn test_parse_type_ref_abs_with_ns() {
    assert_parse(
        parse_type(Span::new("::ns::T")),
        Type::Ref(TypeRef {
            abs: true,
            ns: vec!["ns".to_string()],
            name: "T".to_string(),
            generics: vec![],
        }),
    );
}

#[test]
fn test_parse_type_ref_rel_with_ns2() {
    assert_parse(
        parse_type(Span::new("ns1::ns2::T")),
        Type::Ref(TypeRef {
            abs: false,
            ns: vec!["ns1".to_string(), "ns2".to_string()],
            name: "T".to_string(),
            generics: vec![],
        }),
    );
}

#[test]
fn test_parse_type_ref_abs_with_ns2() {
    assert_parse(
        parse_type(Span::new("::ns1::ns2::T")),
        Type::Ref(TypeRef {
            abs: true,
            ns: vec!["ns1".to_string(), "ns2".to_string()],
            name: "T".to_string(),
            generics: vec![],
        }),
    );
}

#[test]
fn test_parse_type_ref_with_generic_ref() {
    let contents = [
        "Foo<UUID>",
        "Foo <UUID>",
        "Foo< UUID>",
        "Foo<UUID >",
        "Foo<UUID,>",
    ];
    for content in contents.iter() {
        assert_parse(
            parse_type(Span::new(content)),
            Type::Ref(TypeRef {
                abs: false,
                ns: vec![],
                name: "Foo".to_string(),
                generics: vec![Type::Ref(TypeRef {
                    abs: false,
                    name: "UUID".to_string(),
                    ns: vec![],
                    generics: vec![],
                })],
            }),
        );
    }
}

#[test]
fn test_parse_type_ref_with_generic_generic() {
    let contents = [
        "Foo<Bar<UUID>>",
        "Foo <Bar<UUID>>",
        "Foo< Bar<UUID>>",
        "Foo<Bar <UUID>>",
        "Foo<Bar< UUID>>",
        "Foo<Bar<UUID >>",
        "Foo<Bar<UUID> >",
        "Foo<Bar<UUID,>,>",
    ];
    for content in contents.iter() {
        assert_parse(
            parse_type(Span::new(content)),
            Type::Ref(TypeRef {
                abs: false,
                ns: vec![],
                name: "Foo".to_string(),
                generics: vec![Type::Ref(TypeRef {
                    abs: false,
                    ns: vec![],
                    name: "Bar".to_string(),
                    generics: vec![Type::Ref(TypeRef {
                        abs: false,
                        ns: vec![],
                        name: "UUID".to_string(),
                        generics: vec![],
                    })],
                })],
            }),
        );
    }
}

#[test]
fn test_parse_type_array() {
    let contents = ["[UUID]", "[ UUID]", "[UUID ]", "[ UUID ]"];
    for content in contents.iter() {
        assert_parse(
            parse_type(Span::new(content)),
            Type::Array(Box::new(Type::Ref(TypeRef {
                abs: false,
                ns: vec![],
                name: "UUID".to_string(),
                generics: vec![],
            }))),
        );
    }
}

#[test]
fn test_parse_type_map() {
    let contents = [
        "{UUID:String}",
        "{ UUID:String}",
        "{UUID:String }",
        "{UUID :String}",
        "{UUID: String}",
        "{ UUID : String }",
    ];
    for content in contents.iter() {
        assert_parse(
            parse_type(Span::new(content)),
            Type::Map(
                Box::new(Type::Ref(TypeRef {
                    abs: false,
                    ns: vec![],
                    name: "UUID".to_string(),
                    generics: vec![],
                })),
                Box::new(Type::Ref(TypeRef {
                    abs: false,
                    ns: vec![],
                    name: "String".to_string(),
                    generics: vec![],
                })),
            ),
        );
    }
}