Skip to main content

layer_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// NOTE:
5// The "Layer" project is no longer maintained or supported.
6// Its original purpose for personal SDK/APK experimentation and learning
7// has been fulfilled.
8//
9// Please use Ferogram instead:
10// https://github.com/ankit-chaubey/ferogram
11// Ferogram will receive future updates and development, although progress
12// may be slower.
13//
14// Ferogram is an async Telegram MTProto client library written in Rust.
15// Its implementation follows the behaviour of the official Telegram clients,
16// particularly Telegram Desktop and TDLib, and aims to provide a clean and
17// modern async interface for building Telegram clients and tools.
18
19use std::fmt;
20use std::str::FromStr;
21
22use crate::errors::ParamParseError;
23
24/// The type of a definition or a parameter, e.g. `ns.Vector<!X>`.
25#[derive(Clone, Debug, PartialEq, Eq, Hash)]
26pub struct Type {
27    /// Namespace components, e.g. `["upload"]` for `upload.File`.
28    pub namespace: Vec<String>,
29
30    /// The bare type name, e.g. `"Vector"`.
31    pub name: String,
32
33    /// `true` when the first letter of the name is lowercase (bare type).
34    pub bare: bool,
35
36    /// `true` when this type is a generic parameter reference (prefixed with `!`).
37    pub generic_ref: bool,
38
39    /// The generic argument, e.g. `long` in `Vector<long>`.
40    pub generic_arg: Option<Box<Type>>,
41}
42
43impl fmt::Display for Type {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        for ns in &self.namespace {
46            write!(f, "{ns}.")?;
47        }
48        if self.generic_ref {
49            write!(f, "!")?;
50        }
51        write!(f, "{}", self.name)?;
52        if let Some(arg) = &self.generic_arg {
53            write!(f, "<{arg}>")?;
54        }
55        Ok(())
56    }
57}
58
59impl Type {
60    /// Collect all nested generic references into `output`.
61    pub(crate) fn collect_generic_refs<'a>(&'a self, output: &mut Vec<&'a str>) {
62        if self.generic_ref {
63            output.push(&self.name);
64        }
65        if let Some(arg) = &self.generic_arg {
66            arg.collect_generic_refs(output);
67        }
68    }
69}
70
71impl FromStr for Type {
72    type Err = ParamParseError;
73
74    /// Parses a TL type expression such as `ns.Vector<!X>`.
75    ///
76    /// # Examples
77    /// ```
78    /// use layer_tl_parser::tl::Type;
79    /// assert!("Vector<long>".parse::<Type>().is_ok());
80    /// assert!("!X".parse::<Type>().is_ok());
81    /// ```
82    fn from_str(raw: &str) -> Result<Self, Self::Err> {
83        // Strip leading `!` → generic reference
84        let (raw, generic_ref) = match raw.strip_prefix('!') {
85            Some(r) => (r, true),
86            None => (raw, false),
87        };
88
89        // Split off `<generic_arg>`
90        let (name_part, generic_arg) = match raw.split_once('<') {
91            Some((name, rest)) => match rest.strip_suffix('>') {
92                Some(arg) => (name, Some(Box::new(Type::from_str(arg)?))),
93                None => return Err(ParamParseError::InvalidGeneric),
94            },
95            None => (raw, None),
96        };
97
98        // Split namespace from name
99        let (namespace, name) = match name_part.rsplit_once('.') {
100            Some((ns_part, n)) => (ns_part.split('.').map(String::from).collect::<Vec<_>>(), n),
101            None => (Vec::new(), name_part),
102        };
103
104        if namespace.iter().any(|p| p.is_empty()) {
105            return Err(ParamParseError::Empty);
106        }
107
108        let first = name.chars().next().ok_or(ParamParseError::Empty)?;
109        let bare = first.is_ascii_lowercase();
110
111        Ok(Self {
112            namespace,
113            name: name.to_owned(),
114            bare,
115            generic_ref,
116            generic_arg,
117        })
118    }
119}