pilota_thrift_parser/parser/
thrift.rs

1use nom::{
2    IResult,
3    bytes::complete::take_while,
4    character::complete::satisfy,
5    combinator::{eof, map, opt, peek, recognize},
6    multi::many_till,
7    sequence::tuple,
8};
9
10use super::super::{
11    descriptor::{
12        Constant, CppInclude, Enum, Exception, File, Include, Service, Struct, Typedef, Union,
13    },
14    parser::*,
15};
16use crate::{Item, Namespace};
17
18impl Parser for Item {
19    fn parse(input: &str) -> IResult<&str, Self> {
20        let (input, keyword) = peek(recognize(tuple((
21            satisfy(|c| c.is_ascii_alphabetic()),
22            take_while(|c: char| c.is_ascii_alphanumeric() || c == '_'),
23        ))))(input)?;
24        macro_rules! unpack {
25            ($variant: ident) => {{
26                let (rest, item) = $variant::parse(input)?;
27                Ok((rest, Self::$variant(item)))
28            }};
29        }
30        match keyword {
31            "include" => unpack!(Include),
32            "cpp_include" => unpack!(CppInclude),
33            "namespace" => unpack!(Namespace),
34            "typedef" => unpack!(Typedef),
35            "const" => unpack!(Constant),
36            "enum" => unpack!(Enum),
37            "struct" => unpack!(Struct),
38            "union" => unpack!(Union),
39            "exception" => unpack!(Exception),
40            "service" => unpack!(Service),
41            _ => Err(nom::Err::Failure(nom::error::Error::new(
42                input,
43                nom::error::ErrorKind::Fail,
44            ))),
45        }
46    }
47}
48
49impl Parser for File {
50    fn parse(input: &str) -> IResult<&str, File> {
51        let mut t: File = Default::default();
52
53        // support empty file/only comment: skip leading and trailing blank, collect 0
54        // or more items, and verify EOF
55        let (remain, items) = many_till(
56            map(
57                tuple((opt(blank), opt(Item::parse), opt(blank))),
58                |(_, item, _)| item,
59            ),
60            eof,
61        )(input)?;
62
63        t.items = items.0.into_iter().flatten().collect::<Vec<_>>();
64
65        let mut namespaces = t.items.iter().filter_map(|item| {
66            if let Item::Namespace(ns) = item {
67                Some(ns)
68            } else {
69                None
70            }
71        });
72
73        t.package = namespaces
74            .clone()
75            .find_map(|n| {
76                if n.scope.0 == "rs" {
77                    Some(n.name.clone())
78                } else {
79                    None
80                }
81            })
82            .or_else(|| {
83                namespaces.find_map(|n| {
84                    if n.scope.0 == "*" {
85                        Some(n.name.clone())
86                    } else {
87                        None
88                    }
89                })
90            });
91
92        Ok((remain, t))
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_thrift() {
102        let body = r#"
103        namespace go http
104
105        include "base.thrift"
106
107        enum Sex {
108            UNKNOWN = 0,
109            MALE = 1,
110            FEMALE = 2,
111        }
112        
113        struct ReqItem{
114            1: optional i64 id(api.js_conv = '', go.tag = 'json:"MyID" tagexpr:"$<0||$>=100"')
115            2: optional string text='hello world'
116            3: required string x
117        }
118        
119        struct BizCommonParam {
120            1: optional i64 api_version (api.query = 'api_version')
121            2: optional i32 token(api.header = 'token')
122        }
123        
124        struct BizRequest {
125            1: optional i64 v_int64(api.query = 'v_int64', api.vd = "$>0&&$<200")
126            2: optional string text(api.body = 'text')
127            3: optional i32 token(api.header = 'token')
128            4: optional map<i64, ReqItem> req_items_map (api.body='req_items_map')
129            5: optional ReqItem some(api.body = 'some')
130            6: optional list<string> req_items(api.query = 'req_items')
131            7: optional i32 api_version(api.path = 'action')
132            8: optional i64 uid(api.path = 'biz')
133            9: optional list<i64> cids(api.query = 'cids')
134            10: optional list<string> vids(api.query = 'vids')
135            255: base.Base base
136            256: optional BizCommonParam biz_common_param (agw.source='not_body_struct')
137        }
138        
139        struct RspItem{
140            1: optional i64 item_id
141            2: optional string text
142        }
143        
144        struct BizResponse {
145            1: optional string T                             (api.header= 'T') 
146            2: optional map<i64, RspItem> rsp_items           (api.body='rsp_items')
147            3: optional i32 v_enum                       (api.none = '')
148            4: optional list<RspItem> rsp_item_list            (api.body = 'rsp_item_list')
149            5: optional i32 http_code                         (api.http_code = '') 
150            6: optional list<i64> item_count (api.header = 'item_count')
151        }
152        
153        exception Exception{
154            1: i32 code (api.http_code = '') 
155            2: string msg 
156        }
157        
158        service BizService {
159            BizResponse BizMethod1(1: BizRequest req)(api.get = '/life/client/:action/:biz', api.baseurl = 'ib.snssdk.com', api.param = 'true')
160            BizResponse BizMethod2(1: BizRequest req)throws(1: Exception err)(api.post = '/life/client/:action/:biz', api.baseurl = 'ib.snssdk.com', api.param = 'true', api.serializer = 'form')
161            BizResponse BizMethod3(1: BizRequest req)(api.post = '/life/client/:action/:biz/other', api.baseurl = 'ib.snssdk.com', api.param = 'true', api.serializer = 'json')
162        }
163        "#;
164        let (_remain, _res) = File::parse(body).unwrap();
165    }
166
167    #[test]
168    fn test_separator() {
169        let body = r#"typedef i32 MyInt32
170typedef string MyString;
171
172struct TypedefTestStruct {
173  1: MyInt32 field_MyInt32;
174  2: MyString field_MyString;
175  3: i32 field_Int32;
176  4: string field_String;
177};
178
179typedef TypedefTestStruct MyStruct,
180
181const list<string> TEST_LIST = [
182    "hello",
183    "world",
184];
185
186service Service {
187  MyStruct testEpisode(1:MyStruct arg)
188},"#;
189        let (remain, res) = File::parse(body).unwrap();
190        assert!(remain.is_empty());
191        assert_eq!(res.items.len(), 6);
192    }
193
194    #[test]
195    fn test_only_comment() {
196        let body = r#"
197        /*** comment test ***/
198        // comment 1
199
200        # comment 2
201        "#;
202        let (remain, res) = File::parse(body).unwrap();
203        assert!(remain.is_empty());
204        assert_eq!(res.items.len(), 0);
205    }
206}