Skip to main content

ferogram_tl_parser/tl/
definition.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, ParseError};
16use crate::tl::{Category, Flag, Parameter, ParameterType, Type};
17use crate::utils::tl_id;
18
19/// A single TL definition: either a constructor or a function.
20///
21/// For example:
22/// ```text
23/// user#12345 id:long first_name:string = User;
24/// ```
25/// becomes a `Definition` with `name = "user"`, `id = 0x12345`,
26/// `params = [id:long, first_name:string]` and `ty = User`.
27#[derive(Clone, Debug, PartialEq)]
28pub struct Definition {
29    /// Namespace parts.  Empty when the definition is in the global namespace.
30    pub namespace: Vec<String>,
31
32    /// The constructor/method name (e.g. `"user"`, `"messages.sendMessage"`).
33    pub name: String,
34
35    /// 32-bit constructor ID, either parsed from `#XXXXXXXX` or CRC32-derived.
36    pub id: u32,
37
38    /// Ordered list of parameters.
39    pub params: Vec<Parameter>,
40
41    /// The boxed type this definition belongs to (e.g. `User`).
42    pub ty: Type,
43
44    /// Whether this is a data constructor or an RPC function.
45    pub category: Category,
46}
47
48impl Definition {
49    /// Returns `namespace.name` joined with dots.
50    pub fn full_name(&self) -> String {
51        let cap = self.namespace.iter().map(|ns| ns.len() + 1).sum::<usize>() + self.name.len();
52        let mut s = String::with_capacity(cap);
53        for ns in &self.namespace {
54            s.push_str(ns);
55            s.push('.');
56        }
57        s.push_str(&self.name);
58        s
59    }
60}
61
62impl fmt::Display for Definition {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        for ns in &self.namespace {
65            write!(f, "{ns}.")?;
66        }
67        write!(f, "{}#{:x}", self.name, self.id)?;
68
69        // Emit any `{X:Type}` generic parameter defs that appear in params
70        let mut generics: Vec<&str> = Vec::new();
71        for p in &self.params {
72            if let ParameterType::Normal { ty, .. } = &p.ty {
73                ty.collect_generic_refs(&mut generics);
74            }
75        }
76        generics.sort_unstable();
77        generics.dedup();
78        for g in generics {
79            write!(f, " {{{g}:Type}}")?;
80        }
81
82        for p in &self.params {
83            write!(f, " {p}")?;
84        }
85        write!(f, " = {}", self.ty)
86    }
87}
88
89impl FromStr for Definition {
90    type Err = ParseError;
91
92    fn from_str(raw: &str) -> Result<Self, Self::Err> {
93        let raw = raw.trim();
94        if raw.is_empty() {
95            return Err(ParseError::Empty);
96        }
97
98        // Split at `=`
99        let (lhs, ty_str) = raw.split_once('=').ok_or(ParseError::MissingType)?;
100        let lhs = lhs.trim();
101        let ty_str = ty_str.trim().trim_end_matches(';').trim();
102
103        if ty_str.is_empty() {
104            return Err(ParseError::MissingType);
105        }
106
107        let mut ty = Type::from_str(ty_str).map_err(|_| ParseError::MissingType)?;
108
109        // Split head (name + optional id) from parameter tokens
110        let (head, rest) = match lhs.split_once(|c: char| c.is_whitespace()) {
111            Some((h, r)) => (h.trim_end(), r.trim_start()),
112            None => (lhs, ""),
113        };
114
115        // Parse optional `#id`
116        let (full_name, explicit_id) = match head.split_once('#') {
117            Some((n, id)) => (n, Some(id)),
118            None => (head, None),
119        };
120
121        // Parse namespace
122        let (namespace, name) = match full_name.rsplit_once('.') {
123            Some((ns_part, n)) => (ns_part.split('.').map(String::from).collect::<Vec<_>>(), n),
124            None => (Vec::new(), full_name),
125        };
126
127        if namespace.iter().any(|p| p.is_empty()) || name.is_empty() {
128            return Err(ParseError::MissingName);
129        }
130
131        let id = match explicit_id {
132            Some(hex) => u32::from_str_radix(hex.trim(), 16).map_err(ParseError::InvalidId)?,
133            None => tl_id(raw),
134        };
135
136        // Parse parameters
137        let mut type_defs: Vec<String> = Vec::new();
138        let mut flag_defs: Vec<String> = Vec::new();
139
140        let params = rest
141            .split_whitespace()
142            .filter_map(|token| match Parameter::from_str(token) {
143                // `{X:Type}` → record the generic name and skip
144                Err(ParamParseError::TypeDef { name }) => {
145                    type_defs.push(name);
146                    None
147                }
148                Ok(p) => {
149                    match &p {
150                        Parameter {
151                            ty: ParameterType::Flags,
152                            ..
153                        } => {
154                            flag_defs.push(p.name.clone());
155                        }
156                        // Validate generic ref is declared
157                        Parameter {
158                            ty:
159                                ParameterType::Normal {
160                                    ty:
161                                        Type {
162                                            name: tn,
163                                            generic_ref: true,
164                                            ..
165                                        },
166                                    ..
167                                },
168                            ..
169                        } if !type_defs.contains(tn) => {
170                            return Some(Err(ParseError::InvalidParam(
171                                ParamParseError::MissingDef,
172                            )));
173                        }
174                        // Validate flag field is declared
175                        Parameter {
176                            ty:
177                                ParameterType::Normal {
178                                    flag: Some(Flag { name: fn_, .. }),
179                                    ..
180                                },
181                            ..
182                        } if !flag_defs.contains(fn_) => {
183                            return Some(Err(ParseError::InvalidParam(
184                                ParamParseError::MissingDef,
185                            )));
186                        }
187                        _ => {}
188                    }
189                    Some(Ok(p))
190                }
191                Err(ParamParseError::NotImplemented) => Some(Err(ParseError::NotImplemented)),
192                Err(e) => Some(Err(ParseError::InvalidParam(e))),
193            })
194            .collect::<Result<Vec<_>, ParseError>>()?;
195
196        // If the return type is itself a declared generic, mark it
197        if type_defs.contains(&ty.name) {
198            ty.generic_ref = true;
199        }
200
201        Ok(Definition {
202            namespace,
203            name: name.to_owned(),
204            id,
205            params,
206            ty,
207            category: Category::Types, // caller sets the real category
208        })
209    }
210}