pilota_thrift_parser/parser/
thrift.rs1use 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 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}