webwire_cli/idl/
struct.rs

1use nom::{
2    bytes::complete::tag,
3    character::complete::char,
4    combinator::{cut, map, opt},
5    error::context,
6    multi::separated_list0,
7    sequence::{pair, preceded, separated_pair, terminated},
8    IResult,
9};
10
11use crate::common::FilePosition;
12use crate::idl::common::{
13    parse_field_separator, parse_identifier, parse_identifier_with_generics, trailing_comma, ws,
14    ws1, Span,
15};
16use crate::idl::field_option::{parse_field_options, FieldOption};
17use crate::idl::r#type::{parse_type, Type};
18
19#[cfg(test)]
20use crate::idl::common::assert_parse;
21#[cfg(test)]
22use nom::Slice;
23
24#[derive(Debug, PartialEq)]
25pub struct Struct {
26    pub name: String,
27    pub generics: Vec<String>,
28    pub fields: Vec<Field>,
29    pub position: FilePosition,
30}
31
32#[derive(Debug, PartialEq)]
33pub struct Field {
34    pub name: String,
35    pub type_: Type,
36    pub optional: bool,
37    pub options: Vec<FieldOption>,
38    pub position: FilePosition,
39}
40
41pub fn parse_struct(input: Span) -> IResult<Span, Struct> {
42    map(
43        pair(
44            preceded(tag("struct"), preceded(ws1, parse_identifier_with_generics)),
45            parse_fields,
46        ),
47        |((name, generics), fields)| Struct {
48            name,
49            generics,
50            fields,
51            position: input.into(),
52        },
53    )(input)
54}
55
56fn parse_fields(input: Span) -> IResult<Span, Vec<Field>> {
57    context(
58        "fields",
59        preceded(
60            preceded(ws, char('{')),
61            cut(terminated(
62                separated_list0(parse_field_separator, preceded(ws, parse_field)),
63                preceded(trailing_comma, preceded(ws, char('}'))),
64            )),
65        ),
66    )(input)
67}
68
69fn parse_field(input: Span) -> IResult<Span, Field> {
70    map(
71        separated_pair(
72            pair(parse_identifier, opt(preceded(ws, char('?')))),
73            preceded(ws, char(':')),
74            pair(parse_type, opt(parse_field_options)),
75        ),
76        |((name, optional), (type_, options))| Field {
77            name,
78            position: input.into(),
79            optional: optional != None,
80            type_,
81            options: if let Some(options) = options {
82                options
83            } else {
84                vec![]
85            },
86        },
87    )(input)
88}
89
90#[test]
91fn test_parse_field() {
92    use crate::idl::r#type::TypeRef;
93    let contents = ["foo:FooType", "foo: FooType", "foo : FooType"];
94    for content in contents.iter() {
95        assert_parse(
96            parse_field(Span::new(content)),
97            Field {
98                name: "foo".to_string(),
99                position: FilePosition { line: 1, column: 1 },
100                type_: Type::Ref(TypeRef {
101                    abs: false,
102                    ns: vec![],
103                    name: "FooType".to_string(),
104                    generics: vec![],
105                }),
106                optional: false,
107                options: vec![],
108            },
109        );
110    }
111}
112
113#[test]
114fn test_parse_field_optional() {
115    use crate::idl::r#type::TypeRef;
116    let contents = [
117        "foo?:FooType",
118        "foo? :FooType",
119        "foo ?:FooType",
120        "foo ? :FooType",
121    ];
122    for content in contents.iter() {
123        assert_parse(
124            parse_field(Span::new(content)),
125            Field {
126                name: "foo".to_string(),
127                position: FilePosition { line: 1, column: 1 },
128                type_: Type::Ref(TypeRef {
129                    abs: false,
130                    ns: vec![],
131                    name: "FooType".to_string(),
132                    generics: vec![],
133                }),
134                optional: true,
135                options: vec![],
136            },
137        );
138    }
139}
140
141#[test]
142fn test_parse_field_with_options() {
143    use crate::idl::r#type::TypeRef;
144    use crate::idl::value::Value;
145    let contents = [
146        "name:String(length=2..50)",
147        "name :String(length=2..50)",
148        "name: String(length=2..50)",
149        "name:String (length=2..50)",
150        "name:String( length=2..50)",
151        "name:String(length =2..50)",
152        "name:String(length= 2..50)",
153        /*
154        "name:String(length=2 ..50)",
155        "name:String(length=2.. 50)",
156        */
157        "name:String(length=2..50 )",
158    ];
159    for content in contents.iter() {
160        assert_parse(
161            parse_field(Span::new(content)),
162            Field {
163                name: "name".to_string(),
164                position: FilePosition { line: 1, column: 1 },
165                type_: Type::Ref(TypeRef {
166                    abs: false,
167                    ns: vec![],
168                    name: "String".to_string(),
169                    generics: vec![],
170                }),
171                optional: false,
172                options: vec![FieldOption {
173                    name: "length".to_string(),
174                    value: Value::Range(Some(2), Some(50)),
175                }],
176            },
177        );
178    }
179}
180
181#[test]
182fn test_parse_array_field_with_options() {
183    use crate::idl::r#type::TypeRef;
184    use crate::idl::value::Value;
185    let contents = [
186        "items:[String](length=0..32)",
187        "items :[String](length=0..32)",
188        "items: [String](length=0..32)",
189        "items:[String] (length=0..32)",
190        "items:[String]( length=0..32)",
191        "items:[String](length =0..32)",
192        "items:[String](length= 0..32)",
193        "items:[String](length=0..32 )",
194    ];
195    for content in contents.iter() {
196        assert_parse(
197            parse_field(Span::new(content)),
198            Field {
199                name: "items".to_string(),
200                position: FilePosition { line: 1, column: 1 },
201                type_: Type::Array(Box::new(Type::Ref(TypeRef {
202                    abs: false,
203                    ns: vec![],
204                    name: "String".to_string(),
205                    generics: vec![],
206                }))),
207                optional: false,
208                options: vec![FieldOption {
209                    name: "length".to_string(),
210                    value: Value::Range(Some(0), Some(32)),
211                }],
212            },
213        );
214    }
215}
216
217#[test]
218fn test_parse_fields_0() {
219    let contents = ["{}", "{ }", "{,}", "{ ,}", "{, }"];
220    for content in contents.iter() {
221        assert_parse(parse_fields(Span::new(content)), vec![])
222    }
223}
224
225#[test]
226fn test_parse_fields_1() {
227    use crate::idl::r#type::TypeRef;
228    let content = "{foo: Foo}";
229    assert_parse(
230        parse_fields(Span::new(content)),
231        vec![Field {
232            name: "foo".to_owned(),
233            position: FilePosition { line: 1, column: 2 },
234            type_: Type::Ref(TypeRef {
235                abs: false,
236                ns: vec![],
237                name: "Foo".to_owned(),
238                generics: vec![],
239            }),
240            optional: false,
241            options: vec![],
242        }],
243    );
244}
245
246#[test]
247fn test_parse_fields_1_ws_variants() {
248    let contents = ["{foo: Foo}", "{foo:Foo }", "{ foo:Foo}", "{foo:Foo,}"];
249    for content in contents.iter() {
250        let (_, f) = parse_fields(Span::new(content)).unwrap();
251        assert_eq!(f.len(), 1);
252    }
253}
254
255#[test]
256fn test_parse_fields_2() {
257    use crate::idl::r#type::TypeRef;
258    let content = "{ foo: Foo, bar: Bar }";
259    assert_parse(
260        parse_fields(Span::new(content)),
261        vec![
262            Field {
263                name: "foo".to_owned(),
264                position: FilePosition { line: 1, column: 3 },
265                type_: Type::Ref(TypeRef {
266                    abs: false,
267                    ns: vec![],
268                    name: "Foo".to_owned(),
269                    generics: vec![],
270                }),
271                optional: false,
272                options: vec![],
273            },
274            Field {
275                name: "bar".to_owned(),
276                position: FilePosition {
277                    line: 1,
278                    column: 13,
279                },
280                type_: Type::Ref(TypeRef {
281                    abs: false,
282                    ns: vec![],
283                    name: "Bar".to_owned(),
284                    generics: vec![],
285                }),
286                optional: false,
287                options: vec![],
288            },
289        ],
290    );
291}
292
293#[test]
294fn test_parse_fields_2_ws_variants() {
295    let contents = [
296        "{foo:Foo,bar:Bar}",
297        "{ foo:Foo,bar:Bar}",
298        "{foo :Foo,bar:Bar}",
299        "{foo: Foo,bar:Bar}",
300        "{foo:Foo ,bar:Bar}",
301        "{foo:Foo, bar:Bar}",
302        "{foo:Foo,bar :Bar}",
303        "{foo:Foo,bar: Bar}",
304        "{foo:Foo,bar:Bar }",
305        // trailing comma
306        "{foo:Foo,bar:Bar,}",
307    ];
308    for content in contents.iter() {
309        let (_, f) = parse_fields(Span::new(content)).unwrap();
310        assert_eq!(f.len(), 2);
311    }
312}
313
314#[test]
315fn test_parse_struct() {
316    let contents = [
317        "struct Pinger{}",
318        "struct Pinger {}",
319        "struct Pinger{ }",
320        "struct Pinger { }",
321    ];
322    for content in contents.iter() {
323        assert_parse(
324            parse_struct(Span::new(content)),
325            Struct {
326                name: "Pinger".to_string(),
327                position: FilePosition { line: 1, column: 1 },
328                generics: vec![],
329                fields: vec![],
330            },
331        );
332    }
333}
334
335#[test]
336fn test_parse_struct_field_options() {
337    use crate::idl::r#type::TypeRef;
338    use crate::idl::value::Value;
339    let contents = ["struct Person { name: [String] (length=1..50) }"];
340    for content in contents.iter() {
341        assert_parse(
342            parse_struct(Span::new(content)),
343            Struct {
344                name: "Person".to_string(),
345                position: FilePosition { line: 1, column: 1 },
346                generics: vec![],
347                fields: vec![Field {
348                    name: "name".to_string(),
349                    position: FilePosition {
350                        line: 1,
351                        column: 17,
352                    },
353                    type_: Type::Array(Box::new(Type::Ref(TypeRef {
354                        abs: false,
355                        ns: vec![],
356                        name: "String".to_string(),
357                        generics: vec![],
358                    }))),
359                    optional: false,
360                    options: vec![FieldOption {
361                        name: "length".to_string(),
362                        value: Value::Range(Some(1), Some(50)),
363                    }],
364                }],
365            },
366        );
367    }
368}
369
370#[test]
371fn test_parse_struct_invalid() {
372    use nom::error::ErrorKind;
373    let input = Span::new("struct 123fail{}");
374    assert_eq!(
375        parse_struct(input),
376        // FIXME the error position is probably incorrect
377        Err(nom::Err::Error(nom::error::Error {
378            input: input.slice(7..),
379            code: ErrorKind::TakeWhile1
380        }))
381    )
382}
383
384#[test]
385fn test_parse_struct_with_fields() {
386    use crate::idl::r#type::TypeRef;
387    let contents = ["struct Person { name: String, age: Integer }"];
388    for content in contents.iter() {
389        assert_parse(
390            parse_struct(Span::new(content)),
391            Struct {
392                name: "Person".to_string(),
393                position: FilePosition { line: 1, column: 1 },
394                generics: vec![],
395                fields: vec![
396                    Field {
397                        name: "name".to_string(),
398                        position: FilePosition {
399                            line: 1,
400                            column: 17,
401                        },
402                        type_: Type::Ref(TypeRef {
403                            abs: false,
404                            ns: vec![],
405                            name: "String".to_string(),
406                            generics: vec![],
407                        }),
408                        optional: false,
409                        options: vec![],
410                    },
411                    Field {
412                        name: "age".to_string(),
413                        position: FilePosition {
414                            line: 1,
415                            column: 31,
416                        },
417                        type_: Type::Ref(TypeRef {
418                            abs: false,
419                            ns: vec![],
420                            name: "Integer".to_string(),
421                            generics: vec![],
422                        }),
423                        optional: false,
424                        options: vec![],
425                    },
426                ],
427            },
428        )
429    }
430}
431
432#[test]
433fn test_parse_struct_with_fields_ws_variants() {
434    let contents = vec![
435        "struct Person{name:String,age:Integer}",
436        "struct Person {name:String,age:Integer}",
437        "struct Person{ name:String,age:Integer}",
438        "struct Person{name :String,age:Integer}",
439        "struct Person{name: String,age:Integer}",
440        "struct Person{name:String ,age:Integer}",
441        "struct Person{name:String, age:Integer}",
442        "struct Person{name:String,age :Integer}",
443        "struct Person{name:String,age: Integer}",
444        "struct Person{name:String,age:Integer }",
445    ];
446    for content in contents.iter() {
447        let (_, s) = parse_struct(Span::new(content)).unwrap();
448        assert_eq!(s.name, "Person");
449        assert_eq!(s.fields.len(), 2);
450    }
451}
452
453#[test]
454fn test_parse_struct_with_generics() {
455    use crate::idl::r#type::TypeRef;
456    let content = "struct Wrapper<T> { value:T }";
457    assert_parse(
458        parse_struct(Span::new(content)),
459        Struct {
460            name: "Wrapper".to_string(),
461            position: FilePosition { line: 1, column: 1 },
462            generics: vec!["T".to_string()],
463            fields: vec![Field {
464                name: "value".to_string(),
465                position: FilePosition {
466                    line: 1,
467                    column: 21,
468                },
469                type_: Type::Ref(TypeRef {
470                    abs: false,
471                    ns: vec![],
472                    name: "T".to_string(),
473                    generics: vec![],
474                }),
475                optional: false,
476                options: vec![],
477            }],
478        },
479    );
480}
481
482#[test]
483fn test_parse_struct_with_generics_ws_variants() {
484    let contents = vec![
485        "struct Wrapper<T>{value:T}",
486        "struct Wrapper <T>{value:T}",
487        "struct Wrapper< T>{value:T}",
488        "struct Wrapper<T >{value:T}",
489        "struct Wrapper<T> {value:T}",
490        "struct Wrapper<T>{ value:T}",
491        "struct Wrapper<T>{value :T}",
492        "struct Wrapper<T>{value: T}",
493        "struct Wrapper<T>{value:T }",
494        "struct Wrapper<T,>{value:T}",
495        "struct Wrapper<T,>{value:T,}",
496    ];
497    for content in contents.iter() {
498        let (_, s) = parse_struct(Span::new(content)).unwrap();
499        assert_eq!(s.name, "Wrapper");
500        assert_eq!(s.fields.len(), 1);
501    }
502}