Skip to main content

grammers_tl_parser/tl/
parameter.rs

1// Copyright 2020 - developers of the `grammers` project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::fmt;
10use std::str::FromStr;
11
12use crate::errors::ParamParseError;
13use crate::tl::ParameterType;
14
15/// A single parameter, with a name and a type.
16#[derive(Debug, PartialEq, Eq, Hash)]
17pub struct Parameter {
18    /// The name of the parameter.
19    pub name: String,
20
21    /// The type of the parameter.
22    pub ty: ParameterType,
23}
24
25impl fmt::Display for Parameter {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write!(f, "{}:{}", self.name, self.ty)
28    }
29}
30
31impl FromStr for Parameter {
32    type Err = ParamParseError;
33
34    /// Parses a parameter.
35    ///
36    /// # Examples
37    ///
38    /// ```
39    /// use grammers_tl_parser::tl::Parameter;
40    ///
41    /// assert!("foo:flags.0?bar.Baz".parse::<Parameter>().is_ok());
42    /// ```
43    fn from_str(param: &str) -> Result<Self, Self::Err> {
44        // Special case: parse `{X:Type}`
45        if let Some(def) = param.strip_prefix('{') {
46            return Err(if let Some(def) = def.strip_suffix(":Type}") {
47                ParamParseError::TypeDef { name: def.into() }
48            } else {
49                ParamParseError::MissingDef
50            });
51        };
52
53        // Parse `name:type`
54        let (name, ty) = match param.split_once(':') {
55            Some((name, ty)) => (name, ty),
56            None => return Err(ParamParseError::NotImplemented),
57        };
58        if name.is_empty() || ty.is_empty() {
59            return Err(ParamParseError::Empty);
60        }
61
62        Ok(Parameter {
63            name: name.into(),
64            ty: ty.parse()?,
65        })
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::tl::{Flag, Type};
73
74    #[test]
75    fn parse_empty_param() {
76        for param_str in [":noname", "notype:", ":"] {
77            assert_eq!(
78                Parameter::from_str(param_str),
79                Err(ParamParseError::Empty),
80                "Parameter::from_str({param_str:?})"
81            );
82        }
83    }
84
85    #[test]
86    fn parse_unknown_param() {
87        for param_str in ["", "no colon", "colonless"] {
88            assert_eq!(
89                Parameter::from_str(param_str),
90                Err(ParamParseError::NotImplemented),
91                "Parameter::from_str({param_str:?})"
92            );
93        }
94    }
95
96    #[test]
97    fn parse_bad_flags() {
98        for param_str in ["foo:bar?", "foo:?bar?", "foo:bar?baz", "foo:bar.baz?qux"] {
99            assert_eq!(
100                Parameter::from_str(param_str),
101                Err(ParamParseError::InvalidFlag),
102                "Parameter::from_str({param_str:?})"
103            );
104        }
105    }
106
107    #[test]
108    fn parse_bad_generics() {
109        assert_eq!(
110            Parameter::from_str("foo:<bar"),
111            Err(ParamParseError::InvalidGeneric)
112        );
113        assert_eq!(
114            Parameter::from_str("foo:bar<"),
115            Err(ParamParseError::InvalidGeneric)
116        );
117    }
118
119    #[test]
120    fn parse_type_def_param() {
121        assert_eq!(
122            Parameter::from_str("{a:Type}"),
123            Err(ParamParseError::TypeDef { name: "a".into() })
124        );
125    }
126
127    #[test]
128    fn parse_unknown_def_param() {
129        assert_eq!(
130            Parameter::from_str("{a:foo}"),
131            Err(ParamParseError::MissingDef)
132        );
133    }
134
135    #[test]
136    fn parse_valid_param() {
137        assert_eq!(
138            Parameter::from_str("foo:#"),
139            Ok(Parameter {
140                name: "foo".into(),
141                ty: ParameterType::Flags
142            })
143        );
144        assert_eq!(
145            Parameter::from_str("foo:!bar"),
146            Ok(Parameter {
147                name: "foo".into(),
148                ty: ParameterType::Normal {
149                    ty: Type {
150                        namespace: vec![],
151                        name: "bar".into(),
152                        bare: true,
153                        generic_ref: true,
154                        generic_arg: None,
155                    },
156                    flag: None,
157                }
158            })
159        );
160        assert_eq!(
161            Parameter::from_str("foo:bar.1?baz"),
162            Ok(Parameter {
163                name: "foo".into(),
164                ty: ParameterType::Normal {
165                    ty: Type {
166                        namespace: vec![],
167                        name: "baz".into(),
168                        bare: true,
169                        generic_ref: false,
170                        generic_arg: None,
171                    },
172                    flag: Some(Flag {
173                        name: "bar".into(),
174                        index: 1,
175                    }),
176                }
177            })
178        );
179        assert_eq!(
180            Parameter::from_str("foo:bar<baz>"),
181            Ok(Parameter {
182                name: "foo".into(),
183                ty: ParameterType::Normal {
184                    ty: Type {
185                        namespace: vec![],
186                        name: "bar".into(),
187                        bare: true,
188                        generic_ref: false,
189                        generic_arg: Some(Box::new("baz".parse().unwrap())),
190                    },
191                    flag: None,
192                }
193            })
194        );
195        assert_eq!(
196            Parameter::from_str("foo:bar.1?baz<qux>"),
197            Ok(Parameter {
198                name: "foo".into(),
199                ty: ParameterType::Normal {
200                    ty: Type {
201                        namespace: vec![],
202                        name: "baz".into(),
203                        bare: true,
204                        generic_ref: false,
205                        generic_arg: Some(Box::new("qux".parse().unwrap())),
206                    },
207                    flag: Some(Flag {
208                        name: "bar".into(),
209                        index: 1,
210                    }),
211                }
212            })
213        );
214    }
215}