Skip to main content

ferogram_tl_parser/tl/
ty.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2//
3// ferogram: async Telegram MTProto client in Rust
4// https://github.com/ankit-chaubey/ferogram
5//
6// Licensed under either the MIT License or the Apache License 2.0.
7// See the LICENSE-MIT or LICENSE-APACHE file in this repository:
8// https://github.com/ankit-chaubey/ferogram
9//
10// Feel free to use, modify, and share this code.
11// Please keep this notice when redistributing.
12
13use std::fmt;
14use std::str::FromStr;
15
16use crate::errors::ParamParseError;
17
18/// The type of a definition or a parameter, e.g. `ns.Vector<!X>`.
19#[derive(Clone, Debug, PartialEq, Eq, Hash)]
20pub struct Type {
21    /// Namespace components, e.g. `["upload"]` for `upload.File`.
22    pub namespace: Vec<String>,
23
24    /// The bare type name, e.g. `"Vector"`.
25    pub name: String,
26
27    /// `true` when the first letter of the name is lowercase (bare type).
28    pub bare: bool,
29
30    /// `true` when this type is a generic parameter reference (prefixed with `!`).
31    pub generic_ref: bool,
32
33    /// The generic argument, e.g. `long` in `Vector<long>`.
34    pub generic_arg: Option<Box<Type>>,
35}
36
37impl fmt::Display for Type {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        for ns in &self.namespace {
40            write!(f, "{ns}.")?;
41        }
42        if self.generic_ref {
43            write!(f, "!")?;
44        }
45        write!(f, "{}", self.name)?;
46        if let Some(arg) = &self.generic_arg {
47            write!(f, "<{arg}>")?;
48        }
49        Ok(())
50    }
51}
52
53impl Type {
54    /// Collect all nested generic references into `output`.
55    pub(crate) fn collect_generic_refs<'a>(&'a self, output: &mut Vec<&'a str>) {
56        if self.generic_ref {
57            output.push(&self.name);
58        }
59        if let Some(arg) = &self.generic_arg {
60            arg.collect_generic_refs(output);
61        }
62    }
63}
64
65impl FromStr for Type {
66    type Err = ParamParseError;
67
68    /// Parses a TL type expression such as `ns.Vector<!X>`.
69    ///
70    /// # Examples
71    /// ```
72    /// use ferogram_tl_parser::tl::Type;
73    /// assert!("Vector<long>".parse::<Type>().is_ok());
74    /// assert!("!X".parse::<Type>().is_ok());
75    /// ```
76    fn from_str(raw: &str) -> Result<Self, Self::Err> {
77        // Strip leading `!` → generic reference
78        let (raw, generic_ref) = match raw.strip_prefix('!') {
79            Some(r) => (r, true),
80            None => (raw, false),
81        };
82
83        // Split off `<generic_arg>`
84        let (name_part, generic_arg) = match raw.split_once('<') {
85            Some((name, rest)) => match rest.strip_suffix('>') {
86                Some(arg) => (name, Some(Box::new(Type::from_str(arg)?))),
87                None => return Err(ParamParseError::InvalidGeneric),
88            },
89            None => (raw, None),
90        };
91
92        // Split namespace from name
93        let (namespace, name) = match name_part.rsplit_once('.') {
94            Some((ns_part, n)) => (ns_part.split('.').map(String::from).collect::<Vec<_>>(), n),
95            None => (Vec::new(), name_part),
96        };
97
98        if namespace.iter().any(|p| p.is_empty()) {
99            return Err(ParamParseError::Empty);
100        }
101
102        let first = name.chars().next().ok_or(ParamParseError::Empty)?;
103        let bare = first.is_ascii_lowercase();
104
105        Ok(Self {
106            namespace,
107            name: name.to_owned(),
108            bare,
109            generic_ref,
110            generic_arg,
111        })
112    }
113}