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