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}