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