ferogram-tl-parser 0.6.5

Parser for Telegram's Type Language (.tl) schema files
Documentation
/*
 * Copyright (c) 2026 Ankit Chaubey <ankitchaubey.dev@gmail.com>
 * https://github.com/ankit-chaubey
 *
 * Project: ferogram
 * Website: https://ferogram.dev
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
 * This file may not be copied, modified, or distributed except according
 * to those terms.
 */

use std::fmt;
use std::str::FromStr;

use crate::errors::ParamParseError;
use crate::tl::{Flag, Type};

/// The kind of a single TL parameter.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ParameterType {
    /// A flags field (`name:#`). Its value is computed from the optional
    /// parameters at serialization time, not stored directly.
    Flags,

    /// A regular typed parameter, optionally guarded by a flag bit.
    Normal {
        /// The Rust type that this parameter maps to.
        ty: Type,
        /// If `Some`, this parameter only exists when the given flag bit is set.
        flag: Option<Flag>,
    },
}

impl fmt::Display for ParameterType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Flags => write!(f, "#"),
            Self::Normal { ty, flag } => {
                if let Some(fl) = flag {
                    write!(f, "{}.{}?", fl.name, fl.index)?;
                }
                write!(f, "{ty}")
            }
        }
    }
}

impl FromStr for ParameterType {
    type Err = ParamParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Bare flags field
        if s == "#" {
            return Ok(Self::Flags);
        }

        // Possibly `flagname.N?Type`
        if let Some((flag_part, ty_part)) = s.split_once('?') {
            // flag_part must be `name.N`
            let (flag_name, flag_idx) = flag_part
                .split_once('.')
                .ok_or(ParamParseError::InvalidFlag)?;

            if flag_name.is_empty() || flag_idx.is_empty() {
                return Err(ParamParseError::InvalidFlag);
            }

            let index = flag_idx
                .parse::<u32>()
                .map_err(|_| ParamParseError::InvalidFlag)?;

            let ty = Type::from_str(ty_part)?;
            return Ok(Self::Normal {
                ty,
                flag: Some(Flag {
                    name: flag_name.to_owned(),
                    index,
                }),
            });
        }

        // Plain type: must not contain `?`
        if s.contains('?') {
            return Err(ParamParseError::InvalidFlag);
        }

        Ok(Self::Normal {
            ty: Type::from_str(s)?,
            flag: None,
        })
    }
}