Skip to main content

ferogram_tl_parser/tl/
ty.rs

1/*
2 * Copyright (c) 2026 Ankit Chaubey <ankitchaubey.dev@gmail.com>
3 * https://github.com/ankit-chaubey
4 *
5 * Project: ferogram
6 * Website: https://ferogram.dev
7 *
8 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
9 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
10 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
11 * This file may not be copied, modified, or distributed except according
12 * to those terms.
13 */
14
15use std::fmt;
16use std::str::FromStr;
17
18use crate::errors::ParamParseError;
19
20/// The type of a definition or a parameter, e.g. `ns.Vector<!X>`.
21#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub struct Type {
23    /// Namespace components, e.g. `["upload"]` for `upload.File`.
24    pub namespace: Vec<String>,
25
26    /// The bare type name, e.g. `"Vector"`.
27    pub name: String,
28
29    /// `true` when the first letter of the name is lowercase (bare type).
30    pub bare: bool,
31
32    /// `true` when this type is a generic parameter reference (prefixed with `!`).
33    pub generic_ref: bool,
34
35    /// The generic argument, e.g. `long` in `Vector<long>`.
36    pub generic_arg: Option<Box<Type>>,
37}
38
39impl fmt::Display for Type {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        for ns in &self.namespace {
42            write!(f, "{ns}.")?;
43        }
44        if self.generic_ref {
45            write!(f, "!")?;
46        }
47        write!(f, "{}", self.name)?;
48        if let Some(arg) = &self.generic_arg {
49            write!(f, "<{arg}>")?;
50        }
51        Ok(())
52    }
53}
54
55impl Type {
56    /// Collect all nested generic references into `output`.
57    pub(crate) fn collect_generic_refs<'a>(&'a self, output: &mut Vec<&'a str>) {
58        if self.generic_ref {
59            output.push(&self.name);
60        }
61        if let Some(arg) = &self.generic_arg {
62            arg.collect_generic_refs(output);
63        }
64    }
65}
66
67impl FromStr for Type {
68    type Err = ParamParseError;
69
70    /// Parses a TL type expression such as `ns.Vector<!X>`.
71    ///
72    /// # Examples
73    /// ```
74    /// use ferogram_tl_parser::tl::Type;
75    /// assert!("Vector<long>".parse::<Type>().is_ok());
76    /// assert!("!X".parse::<Type>().is_ok());
77    /// ```
78    fn from_str(raw: &str) -> Result<Self, Self::Err> {
79        // Strip leading `!` → generic reference
80        let (raw, generic_ref) = match raw.strip_prefix('!') {
81            Some(r) => (r, true),
82            None => (raw, false),
83        };
84
85        // Split off `<generic_arg>`
86        let (name_part, generic_arg) = match raw.split_once('<') {
87            Some((name, rest)) => match rest.strip_suffix('>') {
88                Some(arg) => (name, Some(Box::new(Type::from_str(arg)?))),
89                None => return Err(ParamParseError::InvalidGeneric),
90            },
91            None => (raw, None),
92        };
93
94        // Split namespace from name
95        let (namespace, name) = match name_part.rsplit_once('.') {
96            Some((ns_part, n)) => (ns_part.split('.').map(String::from).collect::<Vec<_>>(), n),
97            None => (Vec::new(), name_part),
98        };
99
100        if namespace.iter().any(|p| p.is_empty()) {
101            return Err(ParamParseError::Empty);
102        }
103
104        let first = name.chars().next().ok_or(ParamParseError::Empty)?;
105        let bare = first.is_ascii_lowercase();
106
107        Ok(Self {
108            namespace,
109            name: name.to_owned(),
110            bare,
111            generic_ref,
112            generic_arg,
113        })
114    }
115}