Skip to main content

ferogram_tl_parser/tl/
ty.rs

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