1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use nom::{
    bytes::complete::tag,
    character::complete::char,
    combinator::{cut, map},
    error::context,
    multi::separated_list0,
    sequence::{pair, preceded, terminated},
    IResult,
};

use crate::common::FilePosition;
use crate::idl::common::{parse_field_separator, parse_identifier, trailing_comma, ws, ws1, Span};
use crate::idl::method::{parse_method, Method};

#[cfg(test)]
use crate::idl::common::assert_parse;

#[derive(Debug, PartialEq)]
pub struct Service {
    pub name: String,
    pub methods: Vec<Method>,
    pub position: FilePosition,
}

fn parse_methods(input: Span) -> IResult<Span, Vec<Method>> {
    context(
        "methods",
        preceded(
            preceded(ws, char('{')),
            cut(terminated(
                separated_list0(parse_field_separator, preceded(ws, parse_method)),
                preceded(trailing_comma, preceded(ws, char('}'))),
            )),
        ),
    )(input)
}

pub fn parse_service(input: Span) -> IResult<Span, Service> {
    context(
        "service",
        map(
            preceded(
                terminated(tag("service"), ws1),
                cut(pair(parse_identifier, parse_methods)),
            ),
            |(name, methods)| Service {
                name,
                methods,
                position: input.into(),
            },
        ),
    )(input)
}

#[test]
fn test_parse_service_no_endpoints() {
    let contents = [
        // normal whitespaces
        "service Pinger {}",
        // whitespace variants
        "service Pinger{}",
        "service Pinger{ }",
    ];
    for content in contents.iter() {
        assert_parse(
            parse_service(Span::new(content)),
            Service {
                name: "Pinger".to_string(),
                methods: vec![],
                position: FilePosition { line: 1, column: 1 },
            },
        )
    }
}

#[test]
fn test_parse_service() {
    use crate::idl::r#type::{Type, TypeRef};
    let contents = [
        // normal whitespaces
        "service Pinger { ping: None -> None, get_version: None -> String }",
        // whitespace variants
        "service Pinger{ping:None->None,get_version:None->String}",
        "service Pinger {ping:None->None,get_version:None->String}",
        "service Pinger{ ping:None->None,get_version:None->String}",
        "service Pinger{ping :None->None,get_version:None->String}",
        "service Pinger{ping: None->None,get_version:None->String}",
        "service Pinger{ping:None ->None,get_version:None->String}",
        "service Pinger{ping:None-> None,get_version:None->String}",
        "service Pinger{ping:None->None ,get_version:None->String}",
        "service Pinger{ping:None->None, get_version:None->String}",
        "service Pinger{ping:None->None,get_version :None->String}",
        "service Pinger{ping:None->None,get_version: None->String}",
        "service Pinger{ping:None->None,get_version:None ->String}",
        "service Pinger{ping:None->None,get_version:None-> String}",
        "service Pinger{ping:None->None,get_version:None->String }",
    ];
    for content in contents.iter() {
        assert_parse(
            parse_service(Span::new(content)),
            Service {
                name: "Pinger".to_string(),
                position: FilePosition { line: 1, column: 1 },
                methods: vec![
                    Method {
                        name: "ping".to_string(),
                        input: None,
                        output: None,
                    },
                    Method {
                        name: "get_version".to_string(),
                        input: None,
                        output: Some(Type::Ref(TypeRef {
                            abs: false,
                            ns: vec![],
                            name: "String".to_string(),
                            generics: vec![],
                        })),
                    },
                ],
            },
        )
    }
}