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