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