├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── tdlib-tl-gen ├── Cargo.toml └── src │ ├── enums.rs │ ├── functions.rs │ ├── lib.rs │ ├── metadata.rs │ ├── rustifier.rs │ └── types.rs ├── tdlib-tl-parser ├── Cargo.toml └── src │ ├── errors.rs │ ├── lib.rs │ ├── tl │ ├── category.rs │ ├── definition.rs │ ├── mod.rs │ ├── parameter.rs │ └── ty.rs │ └── tl_iterator.rs └── tdlib ├── Cargo.toml ├── build.rs ├── examples └── get_me.rs ├── src ├── generated.rs ├── lib.rs ├── observer.rs └── tdjson.rs └── tl └── api.tl /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | - name: Run cargo build 19 | run: cargo build --features dox 20 | 21 | rustfmt: 22 | name: Rustfmt 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: stable 30 | override: true 31 | components: rustfmt 32 | - name: Run cargo fmt 33 | run: cargo fmt --all -- --check 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "tdlib", 4 | "tdlib-tl-gen", 5 | "tdlib-tl-parser", 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lonami 4 | Copyright (c) 2022 Marco Melorio 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tdlib-rs 2 | 3 | A Rust wrapper around the Telegram Database library. It includes a generator to automatically generate the types and functions from the TDLib's [Type Language](https://core.telegram.org/mtproto/TL) file. 4 | 5 | It's mainly created for using it in the [Telegrand](https://github.com/melix99/telegrand) client, but it should work also for any other Rust project. 6 | 7 | Current supported TDLib version: [1.8.19](https://github.com/tdlib/td/commit/2589c3fd46925f5d57e4ec79233cd1bd0f5d0c09). 8 | 9 | ## Credits 10 | 11 | - [grammers](https://github.com/Lonami/grammers): the `tdlib-tl-gen` and `tdlib-tl-parser` projects are forks of the `grammers-tl-gen` and `grammers-tl-parser` projects. 12 | - [rust-tdlib](https://github.com/aCLr/rust-tdlib): for inspiration about some client code. 13 | -------------------------------------------------------------------------------- /tdlib-tl-gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tdlib-tl-gen" 3 | version = "0.5.0" 4 | authors = ["Marco Melorio "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | homepage = "https://github.com/melix99/tdlib-rs" 8 | repository = "https://github.com/melix99/tdlib-rs" 9 | documentation = "https://docs.rs/tdlib-tl-gen" 10 | description = """ 11 | Rust code generator from TDLib's API definitions. 12 | """ 13 | 14 | [dependencies] 15 | tdlib-tl-parser = { path = "../tdlib-tl-parser", version = "0.2" } 16 | -------------------------------------------------------------------------------- /tdlib-tl-gen/src/enums.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2021 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | //! Code to generate Rust's `enum`'s from TL definitions. 11 | 12 | use crate::ignore_type; 13 | use crate::metadata::Metadata; 14 | use crate::rustifier; 15 | use std::io::{self, Write}; 16 | use tdlib_tl_parser::tl::{Category, Definition, Type}; 17 | 18 | /// Writes an enumeration listing all types such as the following rust code: 19 | /// 20 | /// ```ignore 21 | /// pub enum Name { 22 | /// Variant(crate::types::Name), 23 | /// } 24 | /// ``` 25 | fn write_enum( 26 | file: &mut W, 27 | ty: &Type, 28 | metadata: &Metadata, 29 | gen_bots_only_api: bool, 30 | ) -> io::Result<()> { 31 | writeln!( 32 | file, 33 | " #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]", 34 | )?; 35 | writeln!(file, " #[serde(tag = \"@type\")]")?; 36 | writeln!(file, " pub enum {} {{", rustifier::types::type_name(ty))?; 37 | for d in metadata.defs_with_type(ty) { 38 | if rustifier::definitions::is_for_bots_only(d) && !gen_bots_only_api { 39 | continue; 40 | } 41 | 42 | writeln!( 43 | file, 44 | "{}", 45 | rustifier::definitions::description(d, " ") 46 | )?; 47 | writeln!( 48 | file, 49 | " #[serde(rename(serialize = \"{0}\", deserialize = \"{0}\"))]", 50 | d.name 51 | )?; 52 | write!(file, " {}", rustifier::definitions::variant_name(d))?; 53 | 54 | // Variant with no struct since it has no data and it only adds noise 55 | if d.params.is_empty() { 56 | writeln!(file, ",")?; 57 | continue; 58 | } else { 59 | write!(file, "(")?; 60 | } 61 | 62 | if metadata.is_recursive_def(d) { 63 | write!(file, "Box<")?; 64 | } 65 | write!(file, "{}", rustifier::definitions::qual_name(d))?; 66 | if metadata.is_recursive_def(d) { 67 | write!(file, ">")?; 68 | } 69 | 70 | writeln!(file, "),")?; 71 | } 72 | writeln!(file, " }}")?; 73 | Ok(()) 74 | } 75 | 76 | /// Write the entire module dedicated to enums. 77 | pub(crate) fn write_enums_mod( 78 | mut file: &mut W, 79 | definitions: &[Definition], 80 | metadata: &Metadata, 81 | gen_bots_only_api: bool, 82 | ) -> io::Result<()> { 83 | // Begin outermost mod 84 | writeln!(file, "pub mod enums {{")?; 85 | writeln!(file, " use serde::{{Deserialize, Serialize}};")?; 86 | 87 | let mut enums: Vec<&Type> = definitions 88 | .iter() 89 | .filter(|d| d.category == Category::Types && !ignore_type(&d.ty)) 90 | .map(|d| &d.ty) 91 | .collect(); 92 | enums.dedup(); 93 | 94 | for ty in enums { 95 | write_enum(&mut file, ty, metadata, gen_bots_only_api)?; 96 | } 97 | 98 | // End outermost mod 99 | writeln!(file, "}}") 100 | } 101 | -------------------------------------------------------------------------------- /tdlib-tl-gen/src/functions.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2021 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | //! Code to generate Rust's `fn`'s from TL definitions. 11 | 12 | use crate::metadata::Metadata; 13 | use crate::rustifier; 14 | use std::io::{self, Write}; 15 | use tdlib_tl_parser::tl::{Category, Definition}; 16 | 17 | /// Defines the `function` corresponding to the definition: 18 | /// 19 | /// ```ignore 20 | /// pub async fn name(client_id: i32, field: Type) -> Result { 21 | /// 22 | /// } 23 | /// ``` 24 | fn write_function( 25 | file: &mut W, 26 | def: &Definition, 27 | _metadata: &Metadata, 28 | gen_bots_only_api: bool, 29 | ) -> io::Result<()> { 30 | if rustifier::definitions::is_for_bots_only(def) && !gen_bots_only_api { 31 | return Ok(()); 32 | } 33 | 34 | // Documentation 35 | writeln!(file, "{}", rustifier::definitions::description(def, " "))?; 36 | writeln!(file, " /// # Arguments")?; 37 | for param in def.params.iter() { 38 | if rustifier::parameters::is_for_bots_only(param) && !gen_bots_only_api { 39 | continue; 40 | } 41 | 42 | writeln!( 43 | file, 44 | " /// * `{}` - {}", 45 | rustifier::parameters::attr_name(param), 46 | param.description.replace('\n', "\n /// ") 47 | )?; 48 | } 49 | writeln!( 50 | file, 51 | " /// * `client_id` - The client id to send the request to" 52 | )?; 53 | 54 | // Function 55 | writeln!(file, " #[allow(clippy::too_many_arguments)]")?; 56 | write!( 57 | file, 58 | " pub async fn {}(", 59 | rustifier::definitions::function_name(def) 60 | )?; 61 | for param in def.params.iter() { 62 | if rustifier::parameters::is_for_bots_only(param) && !gen_bots_only_api { 63 | continue; 64 | } 65 | 66 | write!(file, "{}: ", rustifier::parameters::attr_name(param))?; 67 | 68 | let is_optional = rustifier::parameters::is_optional(param); 69 | if is_optional { 70 | write!(file, "Option<")?; 71 | } 72 | write!(file, "{}", rustifier::parameters::qual_name(param))?; 73 | if is_optional { 74 | write!(file, ">")?; 75 | } 76 | 77 | write!(file, ", ")?; 78 | } 79 | 80 | writeln!( 81 | file, 82 | "client_id: i32) -> Result<{}, crate::types::Error> {{", 83 | rustifier::types::qual_name(&def.ty, false) 84 | )?; 85 | 86 | // Compose request 87 | writeln!(file, " let request = json!({{")?; 88 | writeln!(file, " \"@type\": \"{}\",", def.name)?; 89 | for param in def.params.iter() { 90 | if rustifier::parameters::is_for_bots_only(param) && !gen_bots_only_api { 91 | continue; 92 | } 93 | 94 | writeln!( 95 | file, 96 | " \"{0}\": {1},", 97 | param.name, 98 | rustifier::parameters::attr_name(param), 99 | )?; 100 | } 101 | writeln!(file, " }});")?; 102 | 103 | // Send request 104 | writeln!( 105 | file, 106 | " let response = send_request(client_id, request).await;" 107 | )?; 108 | writeln!(file, " if response[\"@type\"] == \"error\" {{")?; 109 | writeln!( 110 | file, 111 | " return Err(serde_json::from_value(response).unwrap())" 112 | )?; 113 | writeln!(file, " }}")?; 114 | 115 | if rustifier::types::is_ok(&def.ty) { 116 | writeln!(file, " Ok(())")?; 117 | } else { 118 | writeln!( 119 | file, 120 | " Ok(serde_json::from_value(response).unwrap())" 121 | )?; 122 | } 123 | 124 | writeln!(file, " }}")?; 125 | Ok(()) 126 | } 127 | 128 | /// Writes an entire definition as Rust code (`fn`). 129 | fn write_definition( 130 | file: &mut W, 131 | def: &Definition, 132 | metadata: &Metadata, 133 | gen_bots_only_api: bool, 134 | ) -> io::Result<()> { 135 | write_function(file, def, metadata, gen_bots_only_api)?; 136 | Ok(()) 137 | } 138 | 139 | /// Write the entire module dedicated to functions. 140 | pub(crate) fn write_functions_mod( 141 | mut file: &mut W, 142 | definitions: &[Definition], 143 | metadata: &Metadata, 144 | gen_bots_only_api: bool, 145 | ) -> io::Result<()> { 146 | // Begin outermost mod 147 | writeln!(file, "pub mod functions {{")?; 148 | writeln!(file, " use serde_json::json;")?; 149 | writeln!(file, " use crate::send_request;")?; 150 | 151 | let functions = definitions 152 | .iter() 153 | .filter(|d| d.category == Category::Functions); 154 | 155 | for definition in functions { 156 | write_definition(&mut file, definition, metadata, gen_bots_only_api)?; 157 | } 158 | 159 | // End outermost mod 160 | writeln!(file, "}}") 161 | } 162 | -------------------------------------------------------------------------------- /tdlib-tl-gen/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2021 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | //! This module gathers all the code generation submodules and coordinates 11 | //! them, feeding them the right data. 12 | mod enums; 13 | mod functions; 14 | mod metadata; 15 | mod rustifier; 16 | mod types; 17 | 18 | use std::io::{self, Write}; 19 | use tdlib_tl_parser::tl::{Definition, Type}; 20 | 21 | /// Don't generate types for definitions of this type, 22 | /// since they are "core" types and treated differently. 23 | const SPECIAL_CASED_TYPES: [&str; 6] = ["Bool", "Bytes", "Int32", "Int53", "Int64", "Ok"]; 24 | 25 | fn ignore_type(ty: &Type) -> bool { 26 | SPECIAL_CASED_TYPES.iter().any(|&x| x == ty.name) 27 | } 28 | 29 | pub fn generate_rust_code( 30 | file: &mut impl Write, 31 | definitions: &[Definition], 32 | gen_bots_only_api: bool, 33 | ) -> io::Result<()> { 34 | write!( 35 | file, 36 | "\ 37 | // Copyright 2020 - developers of the `grammers` project.\n\ 38 | // Copyright 2021 - developers of the `tdlib-rs` project.\n\ 39 | //\n\ 40 | // Licensed under the Apache License, Version 2.0 or the MIT license\n\ 42 | // , at your\n\ 43 | // option. This file may not be copied, modified, or distributed\n\ 44 | // except according to those terms.\n\ 45 | " 46 | )?; 47 | 48 | let metadata = metadata::Metadata::new(definitions); 49 | types::write_types_mod(file, definitions, &metadata, gen_bots_only_api)?; 50 | enums::write_enums_mod(file, definitions, &metadata, gen_bots_only_api)?; 51 | functions::write_functions_mod(file, definitions, &metadata, gen_bots_only_api)?; 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /tdlib-tl-gen/src/metadata.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2022 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | use crate::rustifier; 11 | use std::collections::{HashMap, HashSet}; 12 | use tdlib_tl_parser::tl::{Category, Definition, Type}; 13 | 14 | /// Additional metadata required by several parts of the generation. 15 | pub(crate) struct Metadata<'a> { 16 | recursing_defs: HashSet<&'a String>, 17 | default_impl_defs: HashSet<&'a String>, 18 | defs_with_type: HashMap<&'a String, Vec<&'a Definition>>, 19 | } 20 | 21 | impl<'a> Metadata<'a> { 22 | pub fn new(definitions: &'a [Definition]) -> Self { 23 | let mut metadata = Self { 24 | recursing_defs: HashSet::new(), 25 | default_impl_defs: HashSet::new(), 26 | defs_with_type: HashMap::new(), 27 | }; 28 | 29 | let type_definitions = definitions 30 | .iter() 31 | .filter(|d| d.category == Category::Types) 32 | .collect::>(); 33 | 34 | let type_definition_map = type_definitions 35 | .iter() 36 | .map(|d| (&d.name, d)) 37 | .collect::>(); 38 | 39 | type_definitions.iter().for_each(|d| { 40 | metadata 41 | .defs_with_type 42 | .entry(&d.ty.name) 43 | .or_insert_with(Vec::new) 44 | .push(d); 45 | }); 46 | 47 | type_definitions.iter().for_each(|d| { 48 | if def_self_references(d, d, &metadata.defs_with_type, &mut HashSet::new()) { 49 | metadata.recursing_defs.insert(&d.name); 50 | } 51 | }); 52 | 53 | type_definitions.iter().for_each(|d| { 54 | if def_contains_only_bare_types(d, &type_definition_map) { 55 | metadata.default_impl_defs.insert(&d.name); 56 | } 57 | }); 58 | 59 | metadata 60 | } 61 | 62 | /// Returns `true` if any of the parameters of `Definition` eventually 63 | /// contains the same type as the `Definition` itself (meaning it recurses). 64 | pub fn is_recursive_def(&self, def: &Definition) -> bool { 65 | self.recursing_defs.contains(&def.name) 66 | } 67 | 68 | /// Returns `true` if the `Definition` can implement the trait `Default` 69 | pub fn can_def_implement_default(&self, def: &Definition) -> bool { 70 | self.default_impl_defs.contains(&def.name) 71 | } 72 | 73 | pub fn defs_with_type(&self, ty: &'a Type) -> &Vec<&Definition> { 74 | &self.defs_with_type[&ty.name] 75 | } 76 | } 77 | 78 | fn def_self_references<'a>( 79 | root: &Definition, 80 | check: &'a Definition, 81 | defs_with_type: &'a HashMap<&String, Vec<&Definition>>, 82 | visited: &mut HashSet<&'a String>, 83 | ) -> bool { 84 | visited.insert(&check.name); 85 | for param in check.params.iter() { 86 | if param.ty.name == root.ty.name { 87 | return true; 88 | } 89 | 90 | if let Some(defs) = defs_with_type.get(¶m.ty.name) { 91 | for def in defs { 92 | if visited.contains(&def.name) { 93 | continue; 94 | } 95 | if def_self_references(root, def, defs_with_type, visited) { 96 | return true; 97 | } 98 | } 99 | } 100 | } 101 | 102 | false 103 | } 104 | 105 | fn def_contains_only_bare_types<'a>( 106 | check: &'a Definition, 107 | definition_map: &'a HashMap<&String, &&Definition>, 108 | ) -> bool { 109 | for param in check.params.iter() { 110 | if !rustifier::parameters::is_builtin_type(param) && !param.ty.bare { 111 | return false; 112 | } 113 | 114 | if let Some(def) = definition_map.get(¶m.ty.name) { 115 | if !def_contains_only_bare_types(def, definition_map) { 116 | return false; 117 | } 118 | } 119 | } 120 | 121 | true 122 | } 123 | -------------------------------------------------------------------------------- /tdlib-tl-gen/src/rustifier.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2021 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | //! Several functions to "rustify" names. 11 | //! 12 | //! Each parsed type can have a corresponding "rusty" name, and 13 | //! the method for it can be found in the corresponding submodule: 14 | //! 15 | //! * `type_name` for use after a type definition (`type FooBar`, `enum FooBar`). 16 | //! * `qual_name` for the qualified type name (`crate::foo::BarBaz`). 17 | //! * `variant_name` for use inside `enum` variants (`Foo`). 18 | //! * `item_path` for use as a qualified item path (`Vec::`). 19 | //! * `attr_name` for use as an attribute name (`foo_bar: ()`). 20 | 21 | use tdlib_tl_parser::tl::{Definition, Parameter, Type}; 22 | 23 | /// Get the rusty type name for a certain definition, excluding namespace. 24 | /// 25 | /// For example, transforms `"ns.some_OK_name"` into `"SomeOkName"`. 26 | fn rusty_type_name(name: &str) -> String { 27 | enum Casing { 28 | Upper, 29 | Lower, 30 | Preserve, 31 | } 32 | 33 | let name = if let Some(pos) = name.rfind('.') { 34 | &name[pos + 1..] 35 | } else { 36 | name 37 | }; 38 | 39 | let mut result = String::with_capacity(name.len()); 40 | 41 | name.chars().fold(Casing::Upper, |casing, c| { 42 | if c == '_' { 43 | return Casing::Upper; 44 | } 45 | 46 | match casing { 47 | Casing::Upper => { 48 | result.push(c.to_ascii_uppercase()); 49 | Casing::Lower 50 | } 51 | Casing::Lower => { 52 | result.push(c.to_ascii_lowercase()); 53 | if c.is_ascii_uppercase() { 54 | Casing::Lower 55 | } else { 56 | Casing::Preserve 57 | } 58 | } 59 | Casing::Preserve => { 60 | result.push(c); 61 | if c.is_ascii_uppercase() { 62 | Casing::Lower 63 | } else { 64 | Casing::Preserve 65 | } 66 | } 67 | } 68 | }); 69 | 70 | result 71 | } 72 | 73 | /// Get the rusty documentation from a string. 74 | fn rusty_doc(indent: &str, doc: &str) -> String { 75 | format!( 76 | "{}/// {}", 77 | indent, 78 | doc.replace('\n', &format!("\n{}/// ", indent)) 79 | ) 80 | } 81 | 82 | pub mod definitions { 83 | use super::*; 84 | 85 | pub fn type_name(def: &Definition) -> String { 86 | rusty_type_name(&def.name) 87 | } 88 | 89 | pub fn function_name(def: &Definition) -> String { 90 | let mut result = String::with_capacity(def.name.len()); 91 | 92 | def.name.chars().for_each(|c| { 93 | if c.is_ascii_uppercase() { 94 | result.push('_'); 95 | result.push(c.to_ascii_lowercase()); 96 | } else { 97 | result.push(c); 98 | } 99 | }); 100 | 101 | result 102 | } 103 | 104 | pub fn qual_name(def: &Definition) -> String { 105 | let mut result = String::new(); 106 | result.push_str("crate::types::"); 107 | result.push_str(&type_name(def)); 108 | result 109 | } 110 | 111 | pub fn variant_name(def: &Definition) -> String { 112 | let name = type_name(def); 113 | let ty_name = types::type_name(&def.ty); 114 | 115 | let variant = if name.starts_with(&ty_name) && name.len() > ty_name.len() { 116 | let variant_name = &name[ty_name.len()..]; 117 | if variant_name.chars().next().unwrap().is_ascii_lowercase() { 118 | &name 119 | } else { 120 | variant_name 121 | } 122 | } else { 123 | &name 124 | }; 125 | 126 | match variant { 127 | "" => { 128 | // Use the name from the last uppercase letter 129 | &name[name 130 | .as_bytes() 131 | .iter() 132 | .rposition(|c| c.is_ascii_uppercase()) 133 | .unwrap_or(0)..] 134 | } 135 | "Self" => { 136 | // Use the name from the second-to-last uppercase letter 137 | &name[name 138 | .as_bytes() 139 | .iter() 140 | .take(name.len() - variant.len()) 141 | .rposition(|c| c.is_ascii_uppercase()) 142 | .unwrap_or(0)..] 143 | } 144 | _ => variant, 145 | } 146 | .to_string() 147 | } 148 | 149 | pub fn description(def: &Definition, indent: &str) -> String { 150 | rusty_doc(indent, &def.description) 151 | } 152 | 153 | pub fn is_for_bots_only(def: &Definition) -> bool { 154 | def.description.contains("; for bots only") 155 | } 156 | } 157 | 158 | pub mod types { 159 | use super::*; 160 | 161 | pub(super) fn builtin_type(ty: &Type) -> Option<&'static str> { 162 | Some(match ty.name.as_ref() { 163 | "Bool" => "bool", 164 | "bytes" => "String", 165 | "double" => "f64", 166 | "int32" => "i32", 167 | "int53" => "i64", 168 | "int64" => "i64", 169 | "string" => "String", 170 | "vector" => "Vec", 171 | "Ok" => "()", 172 | _ => return None, 173 | }) 174 | } 175 | 176 | fn get_base_path(ty: &Type) -> String { 177 | if let Some(name) = builtin_type(ty) { 178 | name.to_string() 179 | } else { 180 | let mut result = String::new(); 181 | if ty.bare { 182 | result.push_str("crate::types::"); 183 | } else { 184 | result.push_str("crate::enums::"); 185 | } 186 | result.push_str(&type_name(ty)); 187 | result 188 | } 189 | } 190 | 191 | fn get_path(ty: &Type, optional_generic_arg: bool) -> String { 192 | let mut result = get_base_path(ty); 193 | 194 | if let Some(generic_ty) = &ty.generic_arg { 195 | result.push('<'); 196 | if optional_generic_arg { 197 | result.push_str("Option<"); 198 | } 199 | 200 | result.push_str(&qual_name(generic_ty, false)); 201 | 202 | if optional_generic_arg { 203 | result.push('>'); 204 | } 205 | result.push('>'); 206 | } 207 | 208 | result 209 | } 210 | 211 | pub fn type_name(ty: &Type) -> String { 212 | rusty_type_name(&ty.name) 213 | } 214 | 215 | pub fn qual_name(ty: &Type, optional_generic_arg: bool) -> String { 216 | get_path(ty, optional_generic_arg) 217 | } 218 | 219 | pub fn is_ok(ty: &Type) -> bool { 220 | ty.name == "Ok" 221 | } 222 | 223 | pub(super) fn serde_as(ty: &Type) -> Option { 224 | if ty.name == "int64" { 225 | return Some("DisplayFromStr".into()); 226 | } 227 | 228 | if let Some(generic_arg) = &ty.generic_arg { 229 | if let Some(serde_as) = serde_as(generic_arg) { 230 | let mut result = get_base_path(ty); 231 | 232 | result.push('<'); 233 | result.push_str(&serde_as); 234 | result.push('>'); 235 | 236 | return Some(result); 237 | } 238 | } 239 | 240 | None 241 | } 242 | } 243 | 244 | pub mod parameters { 245 | use super::*; 246 | 247 | pub fn qual_name(param: &Parameter) -> String { 248 | // HACK: We're just matching against specific cases because there's not a 249 | // documented way for knowing optional generic arguments in the tl scheme 250 | let optional_generic_arg = param.description.contains("; messages may be null"); 251 | types::qual_name(¶m.ty, optional_generic_arg) 252 | } 253 | 254 | pub fn attr_name(param: &Parameter) -> String { 255 | match ¶m.name[..] { 256 | "final" => "r#final".into(), 257 | "loop" => "r#loop".into(), 258 | "self" => "is_self".into(), 259 | "static" => "r#static".into(), 260 | "type" => "r#type".into(), 261 | _ => { 262 | let mut result = param.name.clone(); 263 | result[..].make_ascii_lowercase(); 264 | result 265 | } 266 | } 267 | } 268 | 269 | pub fn is_builtin_type(param: &Parameter) -> bool { 270 | types::builtin_type(¶m.ty).is_some() || is_optional(param) 271 | } 272 | 273 | pub fn is_optional(param: &Parameter) -> bool { 274 | param.description.contains("; may be null") || param.description.contains("; pass null") 275 | } 276 | 277 | pub fn is_for_bots_only(param: &Parameter) -> bool { 278 | param.description.contains("; for bots only") 279 | } 280 | 281 | pub fn description(param: &Parameter, indent: &str) -> String { 282 | rusty_doc(indent, ¶m.description) 283 | } 284 | 285 | pub fn serde_as(param: &Parameter) -> Option { 286 | types::serde_as(¶m.ty) 287 | } 288 | } 289 | 290 | #[cfg(test)] 291 | mod tests { 292 | use super::*; 293 | 294 | // Core methods 295 | 296 | #[test] 297 | fn check_rusty_type_name() { 298 | assert_eq!(rusty_type_name("ns.some_OK_name"), "SomeOkName"); 299 | } 300 | 301 | // Definition methods 302 | 303 | #[test] 304 | fn check_def_type_name() { 305 | let def = "userEmpty = User".parse().unwrap(); 306 | let name = definitions::type_name(&def); 307 | assert_eq!(name, "UserEmpty"); 308 | } 309 | 310 | #[test] 311 | fn check_def_qual_name() { 312 | let def = "userEmpty = User".parse().unwrap(); 313 | let name = definitions::qual_name(&def); 314 | assert_eq!(name, "crate::types::UserEmpty"); 315 | } 316 | 317 | #[test] 318 | fn check_def_variant_name() { 319 | let def = "new_session_created = NewSession".parse().unwrap(); 320 | let name = definitions::variant_name(&def); 321 | assert_eq!(name, "Created"); 322 | } 323 | 324 | #[test] 325 | fn check_def_empty_variant_name() { 326 | let def = "true = True".parse().unwrap(); 327 | let name = definitions::variant_name(&def); 328 | assert_eq!(name, "True"); 329 | } 330 | 331 | #[test] 332 | fn check_def_self_variant_name() { 333 | let def = "inputPeerSelf = InputPeer".parse().unwrap(); 334 | let name = definitions::variant_name(&def); 335 | assert_eq!(name, "PeerSelf"); 336 | } 337 | 338 | // Type methods 339 | 340 | #[test] 341 | fn check_type_type_name() { 342 | let ty = "storage.FileType".parse().unwrap(); 343 | let name = types::type_name(&ty); 344 | assert_eq!(name, "FileType"); 345 | } 346 | 347 | #[test] 348 | fn check_type_qual_name() { 349 | let ty = "InputPeer".parse().unwrap(); 350 | let name = types::qual_name(&ty); 351 | assert_eq!(name, "crate::enums::InputPeer"); 352 | } 353 | 354 | #[test] 355 | fn check_type_qual_bare_name() { 356 | let ty = "ipPort".parse().unwrap(); 357 | let name = types::qual_name(&ty); 358 | assert_eq!(name, "crate::types::IpPort"); 359 | } 360 | 361 | #[test] 362 | fn check_type_bytes_qual_name() { 363 | let ty = "bytes".parse().unwrap(); 364 | let name = types::qual_name(&ty); 365 | assert_eq!(name, "Vec"); 366 | } 367 | 368 | #[test] 369 | fn check_type_large_int_qual_name() { 370 | let ty = "int256".parse().unwrap(); 371 | let name = types::qual_name(&ty); 372 | assert_eq!(name, "[u8; 32]"); 373 | } 374 | 375 | #[test] 376 | fn check_type_raw_vec_qual_name() { 377 | let ty = "vector".parse().unwrap(); 378 | let name = types::qual_name(&ty); 379 | assert_eq!(name, "Vec"); 380 | } 381 | 382 | #[test] 383 | fn check_type_vec_qual_name() { 384 | let ty = "Vector".parse().unwrap(); 385 | let name = types::qual_name(&ty); 386 | assert_eq!(name, "Vec"); 387 | } 388 | 389 | // Parameter methods 390 | 391 | #[test] 392 | fn check_param_qual_name() { 393 | let param = "pts:int".parse().unwrap(); 394 | let name = parameters::qual_name(¶m); 395 | assert_eq!(name, "i32"); 396 | } 397 | 398 | #[test] 399 | fn check_param_attr_name() { 400 | let param = "access_hash:long".parse().unwrap(); 401 | let name = parameters::attr_name(¶m); 402 | assert_eq!(name, "access_hash"); 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /tdlib-tl-gen/src/types.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2021 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | //! Code to generate Rust's `struct`'s from TL definitions. 11 | 12 | use crate::ignore_type; 13 | use crate::metadata::Metadata; 14 | use crate::rustifier; 15 | use std::io::{self, Write}; 16 | use tdlib_tl_parser::tl::{Category, Definition}; 17 | 18 | /// Defines the `struct` corresponding to the definition: 19 | /// 20 | /// ```ignore 21 | /// pub struct Name { 22 | /// pub field: Type, 23 | /// } 24 | /// ``` 25 | fn write_struct( 26 | file: &mut W, 27 | def: &Definition, 28 | metadata: &Metadata, 29 | gen_bots_only_api: bool, 30 | ) -> io::Result<()> { 31 | if rustifier::definitions::is_for_bots_only(def) && !gen_bots_only_api { 32 | return Ok(()); 33 | } 34 | 35 | writeln!(file, "{}", rustifier::definitions::description(def, " "))?; 36 | 37 | let serde_as = def 38 | .params 39 | .iter() 40 | .any(|p| rustifier::parameters::serde_as(p).is_some()); 41 | 42 | if serde_as { 43 | writeln!(file, " #[serde_as]",)?; 44 | } 45 | 46 | write!(file, " #[derive(Clone, Debug, ",)?; 47 | if metadata.can_def_implement_default(def) { 48 | write!(file, "Default, ",)?; 49 | } 50 | writeln!(file, "PartialEq, Deserialize, Serialize)]",)?; 51 | 52 | writeln!( 53 | file, 54 | " pub struct {} {{", 55 | rustifier::definitions::type_name(def), 56 | )?; 57 | 58 | for param in def.params.iter() { 59 | if rustifier::parameters::is_for_bots_only(param) && !gen_bots_only_api { 60 | continue; 61 | } 62 | 63 | writeln!( 64 | file, 65 | "{}", 66 | rustifier::parameters::description(param, " ") 67 | )?; 68 | 69 | if let Some(serde_as) = rustifier::parameters::serde_as(param) { 70 | writeln!(file, " #[serde_as(as = \"{}\")]", serde_as)?; 71 | } 72 | write!( 73 | file, 74 | " pub {}: ", 75 | rustifier::parameters::attr_name(param), 76 | )?; 77 | 78 | let is_optional = rustifier::parameters::is_optional(param); 79 | if is_optional { 80 | write!(file, "Option<")?; 81 | } 82 | write!(file, "{}", rustifier::parameters::qual_name(param))?; 83 | if is_optional { 84 | write!(file, ">")?; 85 | } 86 | 87 | writeln!(file, ",")?; 88 | } 89 | 90 | writeln!(file, " }}")?; 91 | Ok(()) 92 | } 93 | 94 | /// Writes an entire definition as Rust code (`struct`). 95 | fn write_definition( 96 | file: &mut W, 97 | def: &Definition, 98 | metadata: &Metadata, 99 | gen_bots_only_api: bool, 100 | ) -> io::Result<()> { 101 | write_struct(file, def, metadata, gen_bots_only_api)?; 102 | Ok(()) 103 | } 104 | 105 | /// Write the entire module dedicated to types. 106 | pub(crate) fn write_types_mod( 107 | mut file: &mut W, 108 | definitions: &[Definition], 109 | metadata: &Metadata, 110 | gen_bots_only_api: bool, 111 | ) -> io::Result<()> { 112 | // Begin outermost mod 113 | writeln!(file, "pub mod types {{")?; 114 | writeln!(file, " use serde::{{Deserialize, Serialize}};")?; 115 | writeln!(file, " use serde_with::{{serde_as, DisplayFromStr}};")?; 116 | 117 | let types = definitions 118 | .iter() 119 | .filter(|d| d.category == Category::Types && !ignore_type(&d.ty) && !d.params.is_empty()); 120 | 121 | for definition in types { 122 | write_definition(&mut file, definition, metadata, gen_bots_only_api)?; 123 | } 124 | 125 | // End outermost mod 126 | writeln!(file, "}}") 127 | } 128 | -------------------------------------------------------------------------------- /tdlib-tl-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tdlib-tl-parser" 3 | version = "0.2.0" 4 | authors = ["Marco Melorio "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | homepage = "https://github.com/melix99/tdlib-rs" 8 | repository = "https://github.com/melix99/tdlib-rs" 9 | documentation = "https://docs.rs/tdlib-tl-parser" 10 | description = """ 11 | A parser for TDLib's Type Language definitions. 12 | """ 13 | -------------------------------------------------------------------------------- /tdlib-tl-parser/src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2022 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | //! Errors that can occur during the parsing of [Type Language] definitions. 11 | //! 12 | //! [Type Language]: https://core.telegram.org/mtproto/TL 13 | 14 | /// The error type for the parsing operation of [`Definition`]s. 15 | /// 16 | /// [`Definition`]: tl/struct.Definition.html 17 | #[derive(Debug, PartialEq, Eq)] 18 | pub enum ParseError { 19 | /// The definition is empty. 20 | Empty, 21 | 22 | /// One of the parameters from this definition was invalid. 23 | InvalidParam(ParamParseError), 24 | 25 | /// The name information is missing from the definition. 26 | MissingName, 27 | 28 | /// The type information is missing from the definition. 29 | MissingType, 30 | 31 | /// The parser does not know how to parse the definition. 32 | NotImplemented, 33 | 34 | /// The file contained an unknown separator (such as `---foo---`) 35 | UnknownSeparator, 36 | } 37 | 38 | /// The error type for the parsing operation of [`Parameter`]s. 39 | /// 40 | /// [`Parameter`]: tl/struct.Parameter.html 41 | #[derive(Debug, PartialEq, Eq)] 42 | pub enum ParamParseError { 43 | /// The parameter was empty. 44 | Empty, 45 | 46 | /// The generic argument was invalid. 47 | InvalidGeneric, 48 | 49 | /// The parser does not know how to parse the parameter. 50 | NotImplemented, 51 | } 52 | -------------------------------------------------------------------------------- /tdlib-tl-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! This library provides a public interface to parse [Type Language] 10 | //! definitions. 11 | //! 12 | //! It exports a single public method, [`parse_tl_file`] to parse entire 13 | //! `.tl` files and yield the definitions it contains. This method will 14 | //! yield [`Definition`]s containing all the information you would possibly 15 | //! need to later use somewhere else (for example, to generate code). 16 | //! 17 | //! [Type Language]: https://core.telegram.org/mtproto/TL 18 | //! [`parse_tl_file`]: fn.parse_tl_file.html 19 | //! [`Definition`]: tl/struct.Definition.html 20 | pub mod errors; 21 | pub mod tl; 22 | mod tl_iterator; 23 | 24 | use errors::ParseError; 25 | use tl::Definition; 26 | use tl_iterator::TlIterator; 27 | 28 | /// Parses a file full of [Type Language] definitions. 29 | /// 30 | /// # Examples 31 | /// 32 | /// ```no_run 33 | /// use std::fs::File; 34 | /// use std::io::{self, Read}; 35 | /// use tdlib_tl_parser::parse_tl_file; 36 | /// 37 | /// fn main() -> std::io::Result<()> { 38 | /// let mut file = File::open("api.tl")?; 39 | /// let mut contents = String::new(); 40 | /// file.read_to_string(&mut contents)?; 41 | /// 42 | /// for definition in parse_tl_file(contents) { 43 | /// dbg!(definition); 44 | /// } 45 | /// 46 | /// Ok(()) 47 | /// } 48 | /// ``` 49 | /// 50 | /// [Type Language]: https://core.telegram.org/mtproto/TL 51 | pub fn parse_tl_file(contents: String) -> impl Iterator> { 52 | TlIterator::new(contents) 53 | } 54 | -------------------------------------------------------------------------------- /tdlib-tl-parser/src/tl/category.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | /// The category to which a definition belongs. 10 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 11 | pub enum Category { 12 | /// The default category, a definition represents a type. 13 | Types, 14 | 15 | /// A definition represents a callable function. 16 | Functions, 17 | } 18 | -------------------------------------------------------------------------------- /tdlib-tl-parser/src/tl/definition.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2022 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | use std::collections::HashMap; 10 | use std::fmt; 11 | use std::str::FromStr; 12 | 13 | use crate::errors::{ParamParseError, ParseError}; 14 | use crate::tl::{Category, Parameter, Type}; 15 | 16 | /// A [Type Language] definition. 17 | /// 18 | /// [Type Language]: https://core.telegram.org/mtproto/TL 19 | #[derive(Debug, PartialEq)] 20 | pub struct Definition { 21 | /// The name of this definition. Also known as "predicate" or "method". 22 | pub name: String, 23 | 24 | /// The description of this definition. 25 | pub description: String, 26 | 27 | /// A possibly-empty list of parameters this definition has. 28 | pub params: Vec, 29 | 30 | /// The type to which this definition belongs to. 31 | pub ty: Type, 32 | 33 | /// The category to which this definition belongs to. 34 | pub category: Category, 35 | } 36 | 37 | impl fmt::Display for Definition { 38 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 39 | write!(f, "{}", self.name)?; 40 | 41 | for param in self.params.iter() { 42 | write!(f, " {}", param)?; 43 | } 44 | write!(f, " = {}", self.ty)?; 45 | Ok(()) 46 | } 47 | } 48 | 49 | impl FromStr for Definition { 50 | type Err = ParseError; 51 | 52 | /// Parses a [Type Language] definition. 53 | /// 54 | /// # Examples 55 | /// 56 | /// ``` 57 | /// use tdlib_tl_parser::tl::Definition; 58 | /// 59 | /// assert!("sendMessage chat_id:int message:string = Message".parse::().is_ok()); 60 | /// ``` 61 | /// 62 | /// [Type Language]: https://core.telegram.org/mtproto/TL 63 | fn from_str(definition: &str) -> Result { 64 | if definition.trim().is_empty() { 65 | return Err(ParseError::Empty); 66 | } 67 | 68 | let (definition, mut docs) = { 69 | let mut docs = HashMap::new(); 70 | let mut comments_end = 0; 71 | 72 | if let Some(start) = definition.rfind("//") { 73 | if let Some(end) = definition[start..].find('\n') { 74 | comments_end = start + end; 75 | } 76 | } 77 | 78 | let mut offset = 0; 79 | while let Some(start) = definition[offset..].find('@') { 80 | let start = start + offset; 81 | let end = if let Some(end) = definition[start + 1..].find('@') { 82 | start + 1 + end 83 | } else { 84 | comments_end 85 | }; 86 | 87 | let comment = definition[start + 1..end].replace("//-", ""); 88 | let comment = comment.replace("//", "").trim().to_owned(); 89 | if let Some((name, content)) = comment.split_once(' ') { 90 | docs.insert(name.into(), content.into()); 91 | } else { 92 | docs.insert(comment, String::new()); 93 | } 94 | 95 | offset = end; 96 | } 97 | 98 | (&definition[comments_end..], docs) 99 | }; 100 | 101 | // Parse `(left = ty)` 102 | let (left, ty) = { 103 | let mut it = definition.split('='); 104 | let ls = it.next().unwrap(); // split() always return at least one 105 | if let Some(t) = it.next() { 106 | (ls.trim(), t.trim()) 107 | } else { 108 | return Err(ParseError::MissingType); 109 | } 110 | }; 111 | 112 | let ty = Type::from_str(ty).map_err(|_| ParseError::MissingType)?; 113 | 114 | // Parse `name middle` 115 | let (name, middle) = { 116 | if let Some(pos) = left.find(' ') { 117 | (left[..pos].trim(), left[pos..].trim()) 118 | } else { 119 | (left.trim(), "") 120 | } 121 | }; 122 | if name.is_empty() { 123 | return Err(ParseError::MissingName); 124 | } 125 | 126 | // Parse `description` 127 | let description = if let Some(description) = docs.remove("description") { 128 | description 129 | } else { 130 | String::new() 131 | }; 132 | 133 | // Parse `middle` 134 | let params = middle 135 | .split_whitespace() 136 | .map(Parameter::from_str) 137 | .filter_map(|p| { 138 | let mut result = match p { 139 | // Any parameter that's okay should just be passed as-is. 140 | Ok(p) => Some(Ok(p)), 141 | 142 | // Unimplenented parameters are unimplemented definitions. 143 | Err(ParamParseError::NotImplemented) => Some(Err(ParseError::NotImplemented)), 144 | 145 | // Any error should just become a `ParseError` 146 | Err(x) => Some(Err(ParseError::InvalidParam(x))), 147 | }; 148 | 149 | if let Some(Ok(ref mut param)) = result { 150 | let name = if param.name == "description" { 151 | "param_description" 152 | } else { 153 | ¶m.name 154 | }; 155 | 156 | if let Some(description) = docs.remove(name) { 157 | param.description = description; 158 | } 159 | } 160 | 161 | result 162 | }) 163 | .collect::>()?; 164 | 165 | Ok(Definition { 166 | name: name.into(), 167 | description, 168 | params, 169 | ty, 170 | category: Category::Types, 171 | }) 172 | } 173 | } 174 | 175 | #[cfg(test)] 176 | mod tests { 177 | use super::*; 178 | 179 | #[test] 180 | fn parse_empty_def() { 181 | assert_eq!(Definition::from_str(""), Err(ParseError::Empty)); 182 | } 183 | 184 | #[test] 185 | fn parse_no_name() { 186 | assert_eq!(Definition::from_str(" = foo"), Err(ParseError::MissingName)); 187 | } 188 | 189 | #[test] 190 | fn parse_no_type() { 191 | assert_eq!(Definition::from_str("foo"), Err(ParseError::MissingType)); 192 | assert_eq!(Definition::from_str("foo = "), Err(ParseError::MissingType)); 193 | } 194 | 195 | #[test] 196 | fn parse_unimplemented() { 197 | assert_eq!( 198 | Definition::from_str("int ? = Int"), 199 | Err(ParseError::NotImplemented) 200 | ); 201 | } 202 | 203 | #[test] 204 | fn parse_valid_definition() { 205 | let def = Definition::from_str("a=d").unwrap(); 206 | assert_eq!(def.name, "a"); 207 | assert_eq!(def.params.len(), 0); 208 | assert_eq!( 209 | def.ty, 210 | Type { 211 | name: "d".into(), 212 | bare: true, 213 | generic_arg: None, 214 | } 215 | ); 216 | 217 | let def = Definition::from_str("a=d").unwrap(); 218 | assert_eq!(def.name, "a"); 219 | assert_eq!(def.params.len(), 0); 220 | assert_eq!( 221 | def.ty, 222 | Type { 223 | name: "d".into(), 224 | bare: true, 225 | generic_arg: Some(Box::new("e".parse().unwrap())), 226 | } 227 | ); 228 | 229 | let def = Definition::from_str("a b:c = d").unwrap(); 230 | assert_eq!(def.name, "a"); 231 | assert_eq!(def.params.len(), 1); 232 | assert_eq!( 233 | def.ty, 234 | Type { 235 | name: "d".into(), 236 | bare: true, 237 | generic_arg: None, 238 | } 239 | ); 240 | } 241 | 242 | #[test] 243 | fn parse_multiline_definition() { 244 | let def = " 245 | first lol:param 246 | = t; 247 | "; 248 | 249 | assert_eq!(Definition::from_str(def).unwrap().name, "first"); 250 | 251 | let def = " 252 | second 253 | lol:String 254 | = t; 255 | "; 256 | 257 | assert_eq!(Definition::from_str(def).unwrap().name, "second"); 258 | 259 | let def = " 260 | third 261 | 262 | lol:String 263 | 264 | = 265 | t; 266 | "; 267 | 268 | assert_eq!(Definition::from_str(def).unwrap().name, "third"); 269 | } 270 | 271 | #[test] 272 | fn parse_complete() { 273 | let def = " 274 | //@description This is a test description 275 | name pname:Vector = Type"; 276 | assert_eq!( 277 | Definition::from_str(def), 278 | Ok(Definition { 279 | name: "name".into(), 280 | description: "This is a test description".into(), 281 | params: vec![Parameter { 282 | name: "pname".into(), 283 | ty: Type { 284 | name: "Vector".into(), 285 | bare: false, 286 | generic_arg: Some(Box::new(Type { 287 | name: "X".into(), 288 | bare: false, 289 | generic_arg: None, 290 | })), 291 | }, 292 | description: String::new(), 293 | },], 294 | ty: Type { 295 | name: "Type".into(), 296 | bare: false, 297 | generic_arg: None, 298 | }, 299 | category: Category::Types, 300 | }) 301 | ); 302 | } 303 | 304 | #[test] 305 | fn test_to_string() { 306 | let def = "name pname:Vector = Type"; 307 | assert_eq!(Definition::from_str(def).unwrap().to_string(), def); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /tdlib-tl-parser/src/tl/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2022 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | //! This module contains all the different structures representing the 11 | //! various terms of the [Type Language]. 12 | //! 13 | //! [Type Language]: https://core.telegram.org/mtproto/TL 14 | mod category; 15 | mod definition; 16 | mod parameter; 17 | mod ty; 18 | 19 | pub use category::Category; 20 | pub use definition::Definition; 21 | pub use parameter::Parameter; 22 | pub use ty::Type; 23 | -------------------------------------------------------------------------------- /tdlib-tl-parser/src/tl/parameter.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2022 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | use std::fmt; 10 | use std::str::FromStr; 11 | 12 | use crate::errors::ParamParseError; 13 | use crate::tl::Type; 14 | 15 | /// A single parameter, with a name and a type. 16 | #[derive(Debug, PartialEq)] 17 | pub struct Parameter { 18 | /// The name of the parameter. 19 | pub name: String, 20 | 21 | /// The type of the parameter. 22 | pub ty: Type, 23 | 24 | /// The description of the parameter. 25 | pub description: String, 26 | } 27 | 28 | impl fmt::Display for Parameter { 29 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 30 | write!(f, "{}:{}", self.name, self.ty) 31 | } 32 | } 33 | 34 | impl FromStr for Parameter { 35 | type Err = ParamParseError; 36 | 37 | /// Parses a parameter. 38 | /// 39 | /// # Examples 40 | /// 41 | /// ``` 42 | /// use tdlib_tl_parser::tl::Parameter; 43 | /// 44 | /// assert!("foo:Bar".parse::().is_ok()); 45 | /// ``` 46 | fn from_str(param: &str) -> Result { 47 | // Parse `name:type` 48 | let (name, ty) = { 49 | let mut it = param.split(':'); 50 | if let Some(n) = it.next() { 51 | if let Some(t) = it.next() { 52 | (n, t) 53 | } else { 54 | return Err(ParamParseError::NotImplemented); 55 | } 56 | } else { 57 | return Err(ParamParseError::Empty); 58 | } 59 | }; 60 | 61 | if name.is_empty() { 62 | return Err(ParamParseError::Empty); 63 | } 64 | 65 | Ok(Parameter { 66 | name: name.into(), 67 | ty: ty.parse()?, 68 | description: String::new(), 69 | }) 70 | } 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | use crate::tl::Type; 77 | 78 | #[test] 79 | fn parse_empty_param() { 80 | assert_eq!(Parameter::from_str(":noname"), Err(ParamParseError::Empty)); 81 | assert_eq!(Parameter::from_str("notype:"), Err(ParamParseError::Empty)); 82 | assert_eq!(Parameter::from_str(":"), Err(ParamParseError::Empty)); 83 | } 84 | 85 | #[test] 86 | fn parse_unknown_param() { 87 | assert_eq!( 88 | Parameter::from_str(""), 89 | Err(ParamParseError::NotImplemented) 90 | ); 91 | assert_eq!( 92 | Parameter::from_str("no colon"), 93 | Err(ParamParseError::NotImplemented) 94 | ); 95 | assert_eq!( 96 | Parameter::from_str("colonless"), 97 | Err(ParamParseError::NotImplemented) 98 | ); 99 | } 100 | 101 | #[test] 102 | fn parse_bad_generics() { 103 | assert_eq!( 104 | Parameter::from_str("foo:"), 117 | Ok(Parameter { 118 | name: "foo".into(), 119 | ty: Type { 120 | name: "bar".into(), 121 | bare: true, 122 | generic_arg: Some(Box::new("baz".parse().unwrap())), 123 | }, 124 | description: String::new(), 125 | }) 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tdlib-tl-parser/src/tl/ty.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2022 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | use std::fmt; 10 | use std::str::FromStr; 11 | 12 | use crate::errors::ParamParseError; 13 | 14 | /// The type of a definition or a parameter. 15 | #[derive(Debug, PartialEq)] 16 | pub struct Type { 17 | /// The name of the type. 18 | pub name: String, 19 | 20 | /// Whether this type is bare or boxed. 21 | pub bare: bool, 22 | 23 | /// If the type has a generic argument, which is its type. 24 | pub generic_arg: Option>, 25 | } 26 | 27 | impl fmt::Display for Type { 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 | write!(f, "{}", self.name)?; 30 | if let Some(generic_arg) = &self.generic_arg { 31 | write!(f, "<{}>", generic_arg)?; 32 | } 33 | Ok(()) 34 | } 35 | } 36 | 37 | impl FromStr for Type { 38 | type Err = ParamParseError; 39 | 40 | /// Parses a type. 41 | /// 42 | /// # Examples 43 | /// 44 | /// ``` 45 | /// use tdlib_tl_parser::tl::Type; 46 | /// 47 | /// assert!("vector".parse::().is_ok()); 48 | /// ``` 49 | fn from_str(ty: &str) -> Result { 50 | // Parse `type` 51 | let (ty, generic_arg) = if let Some(pos) = ty.find('<') { 52 | if !ty.ends_with('>') { 53 | return Err(ParamParseError::InvalidGeneric); 54 | } 55 | ( 56 | &ty[..pos], 57 | Some(Box::new(Type::from_str(&ty[pos + 1..ty.len() - 1])?)), 58 | ) 59 | } else { 60 | (ty, None) 61 | }; 62 | 63 | if ty.is_empty() { 64 | return Err(ParamParseError::Empty); 65 | } 66 | 67 | // Safe to unwrap because we just checked is not empty 68 | let bare = ty.chars().next().unwrap().is_ascii_lowercase(); 69 | 70 | Ok(Self { 71 | name: ty.into(), 72 | bare, 73 | generic_arg, 74 | }) 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | 82 | #[test] 83 | fn check_empty_simple() { 84 | assert_eq!(Type::from_str(""), Err(ParamParseError::Empty)); 85 | } 86 | 87 | #[test] 88 | fn check_simple() { 89 | assert_eq!( 90 | Type::from_str("foo"), 91 | Ok(Type { 92 | name: "foo".into(), 93 | bare: true, 94 | generic_arg: None, 95 | }) 96 | ); 97 | } 98 | 99 | #[test] 100 | fn check_empty() { 101 | assert_eq!(Type::from_str(""), Err(ParamParseError::Empty)); 102 | } 103 | 104 | #[test] 105 | fn check_bare() { 106 | assert!(match Type::from_str("foo") { 107 | Ok(Type { bare: true, .. }) => true, 108 | _ => false, 109 | }); 110 | assert!(match Type::from_str("Foo") { 111 | Ok(Type { bare: false, .. }) => true, 112 | _ => false, 113 | }); 114 | } 115 | 116 | #[test] 117 | fn check_generic_arg() { 118 | assert!(match Type::from_str("foo") { 119 | Ok(Type { 120 | generic_arg: None, .. 121 | }) => true, 122 | _ => false, 123 | }); 124 | assert!(match Type::from_str("foo") { 125 | Ok(Type { 126 | generic_arg: Some(x), 127 | .. 128 | }) => *x == "bar".parse().unwrap(), 129 | _ => false, 130 | }); 131 | assert!(match Type::from_str("foo") { 132 | Ok(Type { 133 | generic_arg: Some(x), 134 | .. 135 | }) => *x == "bar".parse().unwrap(), 136 | _ => false, 137 | }); 138 | assert!(match Type::from_str("foo>") { 139 | Ok(Type { 140 | generic_arg: Some(x), 141 | .. 142 | }) => *x == "bar".parse().unwrap(), 143 | _ => false, 144 | }); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tdlib-tl-parser/src/tl_iterator.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2021 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | use crate::errors::ParseError; 10 | use crate::tl::{Category, Definition}; 11 | 12 | const DEFINITION_SEP: char = ';'; 13 | const FUNCTIONS_SEP: &str = "---functions---"; 14 | const TYPES_SEP: &str = "---types---"; 15 | 16 | /// An iterator over [Type Language] definitions. 17 | /// 18 | /// [Type Language]: https://core.telegram.org/mtproto/TL 19 | pub struct TlIterator { 20 | contents: String, 21 | index: usize, 22 | category: Category, 23 | } 24 | 25 | impl TlIterator { 26 | pub(crate) fn new(contents: String) -> Self { 27 | TlIterator { 28 | contents, 29 | index: 0, 30 | category: Category::Types, 31 | } 32 | } 33 | } 34 | 35 | impl Iterator for TlIterator { 36 | type Item = Result; 37 | 38 | fn next(&mut self) -> Option { 39 | let definition = loop { 40 | if self.index >= self.contents.len() { 41 | return None; 42 | } 43 | 44 | let (end, is_empty) = { 45 | let mut chars = self.contents[self.index..].char_indices().peekable(); 46 | let mut in_comment = false; 47 | let mut is_empty = true; 48 | 49 | loop { 50 | if let Some((i, c)) = chars.next() { 51 | if !in_comment && c == '/' { 52 | if let Some((_, pc)) = chars.peek() { 53 | if *pc == '/' { 54 | in_comment = true; 55 | } 56 | } 57 | } else if in_comment && c == '\n' { 58 | in_comment = false; 59 | } 60 | 61 | if !in_comment { 62 | if !c.is_whitespace() { 63 | is_empty = false; 64 | } 65 | 66 | if c == DEFINITION_SEP { 67 | break (self.index + i, is_empty); 68 | } 69 | } 70 | } else { 71 | break (self.contents.len(), is_empty); 72 | } 73 | } 74 | }; 75 | 76 | let definition = self.contents[self.index..end].trim(); 77 | self.index = end + DEFINITION_SEP.len_utf8(); 78 | 79 | if !is_empty { 80 | break definition; 81 | } 82 | }; 83 | 84 | // Get rid of the leading separator and adjust category 85 | let definition = if definition.starts_with("---") { 86 | if let Some(definition) = definition.strip_prefix(FUNCTIONS_SEP) { 87 | self.category = Category::Functions; 88 | definition.trim() 89 | } else if let Some(definition) = definition.strip_prefix(TYPES_SEP) { 90 | self.category = Category::Types; 91 | definition.trim() 92 | } else { 93 | return Some(Err(ParseError::UnknownSeparator)); 94 | } 95 | } else { 96 | definition 97 | }; 98 | 99 | // Yield the fixed definition 100 | Some(match definition.parse::() { 101 | Ok(mut d) => { 102 | d.category = self.category; 103 | Ok(d) 104 | } 105 | x => x, 106 | }) 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | mod tests { 112 | use super::*; 113 | use crate::errors::ParseError; 114 | 115 | #[test] 116 | fn parse_bad_separator() { 117 | let mut it = TlIterator::new("---foo---".into()); 118 | assert_eq!(it.next(), Some(Err(ParseError::UnknownSeparator))); 119 | assert_eq!(it.next(), None); 120 | } 121 | 122 | #[test] 123 | fn parse_file() { 124 | let mut it = TlIterator::new( 125 | " 126 | // leading; comment 127 | first = t; // inline comment 128 | second and bad; 129 | third = t; 130 | // trailing comment 131 | " 132 | .into(), 133 | ); 134 | 135 | assert_eq!(it.next().unwrap().unwrap().name, "first"); 136 | assert!(it.next().unwrap().is_err()); 137 | assert_eq!(it.next().unwrap().unwrap().name, "third"); 138 | assert_eq!(it.next(), None); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tdlib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tdlib" 3 | version = "0.10.0" 4 | authors = ["Marco Melorio "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | homepage = "https://github.com/melix99/tdlib-rs" 8 | repository = "https://github.com/melix99/tdlib-rs" 9 | documentation = "https://docs.rs/tdlib" 10 | description = """ 11 | Rust wrapper around the Telegram Database Library. 12 | """ 13 | 14 | [package.metadata.docs.rs] 15 | features = ["dox"] 16 | 17 | [package.metadata.system-deps] 18 | tdjson = "1.8.19" 19 | 20 | [features] 21 | default = ["bots-only-api"] 22 | dox = [] 23 | bots-only-api = [] 24 | 25 | [dependencies] 26 | log = "0.4" 27 | futures-channel = "0.3" 28 | once_cell = "1.18" 29 | serde = { version = "1.0", features = ["derive"] } 30 | serde_json = "1.0" 31 | serde_with = "3.2" 32 | 33 | [build-dependencies] 34 | system-deps = "6" 35 | tdlib-tl-gen = { path = "../tdlib-tl-gen", version = "0.5" } 36 | tdlib-tl-parser = { path = "../tdlib-tl-parser", version = "0.2" } 37 | 38 | [dev-dependencies] 39 | tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync"] } 40 | -------------------------------------------------------------------------------- /tdlib/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - developers of the `tdlib-rs` project. 2 | // Copyright 2020 - developers of the `grammers` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | use std::env; 10 | use std::fs::File; 11 | use std::io::{self, BufWriter, Read, Write}; 12 | use std::path::Path; 13 | use tdlib_tl_gen::generate_rust_code; 14 | use tdlib_tl_parser::parse_tl_file; 15 | use tdlib_tl_parser::tl::Definition; 16 | 17 | /// Load the type language definitions from a certain file. 18 | /// Parse errors will be printed to `stderr`, and only the 19 | /// valid results will be returned. 20 | fn load_tl(file: &str) -> io::Result> { 21 | let mut file = File::open(file)?; 22 | let mut contents = String::new(); 23 | file.read_to_string(&mut contents)?; 24 | Ok(parse_tl_file(contents) 25 | .into_iter() 26 | .filter_map(|d| match d { 27 | Ok(d) => Some(d), 28 | Err(e) => { 29 | eprintln!("TL: parse error: {:?}", e); 30 | None 31 | } 32 | }) 33 | .collect()) 34 | } 35 | 36 | fn main() -> std::io::Result<()> { 37 | // Prevent linking libraries to avoid documentation failure 38 | #[cfg(not(feature = "dox"))] 39 | system_deps::Config::new().probe().unwrap(); 40 | 41 | let definitions = load_tl("tl/api.tl")?; 42 | 43 | let mut file = BufWriter::new(File::create( 44 | Path::new(&env::var("OUT_DIR").unwrap()).join("generated.rs"), 45 | )?); 46 | 47 | generate_rust_code(&mut file, &definitions, cfg!(feature = "bots-only-api"))?; 48 | 49 | file.flush()?; 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /tdlib/examples/get_me.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{AtomicBool, Ordering}, 3 | Arc, 4 | }; 5 | use tdlib::{ 6 | enums::{AuthorizationState, Update, User}, 7 | functions, 8 | }; 9 | use tokio::sync::mpsc::{self, Receiver, Sender}; 10 | 11 | fn ask_user(string: &str) -> String { 12 | println!("{}", string); 13 | let mut input = String::new(); 14 | std::io::stdin().read_line(&mut input).unwrap(); 15 | input.trim().to_string() 16 | } 17 | 18 | async fn handle_update(update: Update, auth_tx: &Sender) { 19 | match update { 20 | Update::AuthorizationState(update) => { 21 | auth_tx.send(update.authorization_state).await.unwrap(); 22 | } 23 | _ => (), 24 | } 25 | } 26 | 27 | async fn handle_authorization_state( 28 | client_id: i32, 29 | mut auth_rx: Receiver, 30 | run_flag: Arc, 31 | ) -> Receiver { 32 | while let Some(state) = auth_rx.recv().await { 33 | match state { 34 | AuthorizationState::WaitTdlibParameters => { 35 | let response = functions::set_tdlib_parameters( 36 | false, 37 | "get_me_db".into(), 38 | String::new(), 39 | String::new(), 40 | false, 41 | false, 42 | false, 43 | false, 44 | env!("API_ID").parse().unwrap(), 45 | env!("API_HASH").into(), 46 | "en".into(), 47 | "Desktop".into(), 48 | String::new(), 49 | env!("CARGO_PKG_VERSION").into(), 50 | false, 51 | true, 52 | client_id, 53 | ) 54 | .await; 55 | 56 | if let Err(error) = response { 57 | println!("{}", error.message); 58 | } 59 | } 60 | AuthorizationState::WaitPhoneNumber => loop { 61 | let input = ask_user("Enter your phone number (include the country calling code):"); 62 | let response = 63 | functions::set_authentication_phone_number(input, None, client_id).await; 64 | match response { 65 | Ok(_) => break, 66 | Err(e) => println!("{}", e.message), 67 | } 68 | }, 69 | AuthorizationState::WaitCode(_) => loop { 70 | let input = ask_user("Enter the verification code:"); 71 | let response = functions::check_authentication_code(input, client_id).await; 72 | match response { 73 | Ok(_) => break, 74 | Err(e) => println!("{}", e.message), 75 | } 76 | }, 77 | AuthorizationState::Ready => { 78 | break; 79 | } 80 | AuthorizationState::Closed => { 81 | // Set the flag to false to stop receiving updates from the 82 | // spawned task 83 | run_flag.store(false, Ordering::Release); 84 | break; 85 | } 86 | _ => (), 87 | } 88 | } 89 | 90 | auth_rx 91 | } 92 | 93 | #[tokio::main] 94 | async fn main() { 95 | // Create the client object 96 | let client_id = tdlib::create_client(); 97 | 98 | // Create a mpsc channel for handling AuthorizationState updates separately 99 | // from the task 100 | let (auth_tx, auth_rx) = mpsc::channel(5); 101 | 102 | // Create a flag to make it possible to stop receiving updates 103 | let run_flag = Arc::new(AtomicBool::new(true)); 104 | let run_flag_clone = run_flag.clone(); 105 | 106 | // Spawn a task to receive updates/responses 107 | let handle = tokio::spawn(async move { 108 | while run_flag_clone.load(Ordering::Acquire) { 109 | if let Some((update, _client_id)) = tdlib::receive() { 110 | handle_update(update, &auth_tx).await; 111 | } 112 | } 113 | }); 114 | 115 | // Set a fairly low verbosity level. We mainly do this because tdlib 116 | // requires to perform a random request with the client to start receiving 117 | // updates for it. 118 | functions::set_log_verbosity_level(2, client_id) 119 | .await 120 | .unwrap(); 121 | 122 | // Handle the authorization state to authenticate the client 123 | let auth_rx = handle_authorization_state(client_id, auth_rx, run_flag.clone()).await; 124 | 125 | // Run the get_me() method to get user information 126 | let User::User(me) = functions::get_me(client_id).await.unwrap(); 127 | println!("Hi, I'm {}", me.first_name); 128 | 129 | // Tell the client to close 130 | functions::close(client_id).await.unwrap(); 131 | 132 | // Handle the authorization state to wait for the "Closed" state 133 | handle_authorization_state(client_id, auth_rx, run_flag.clone()).await; 134 | 135 | // Wait for the previously spawned task to end the execution 136 | handle.await.unwrap(); 137 | } 138 | -------------------------------------------------------------------------------- /tdlib/src/generated.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2021 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | include!(concat!(env!("OUT_DIR"), "/generated.rs")); 10 | -------------------------------------------------------------------------------- /tdlib/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 - developers of the `grammers` project. 2 | // Copyright 2021 - developers of the `tdlib-rs` project. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | mod generated; 10 | mod observer; 11 | mod tdjson; 12 | 13 | pub use generated::{enums, functions, types}; 14 | 15 | use enums::Update; 16 | use once_cell::sync::Lazy; 17 | use serde_json::Value; 18 | use std::sync::atomic::{AtomicU32, Ordering}; 19 | 20 | static EXTRA_COUNTER: AtomicU32 = AtomicU32::new(0); 21 | static OBSERVER: Lazy = Lazy::new(observer::Observer::new); 22 | 23 | /// Create a TdLib client returning its id. Note that to start receiving 24 | /// updates for a client you need to send at least a request with it first. 25 | pub fn create_client() -> i32 { 26 | tdjson::create_client() 27 | } 28 | 29 | /// Receive a single update or response from TdLib. If it's an update, it 30 | /// returns a tuple with the `Update` and the associated `client_id`. 31 | /// Note that to start receiving updates for a client you need to send 32 | /// at least a request with it first. 33 | pub fn receive() -> Option<(Update, i32)> { 34 | let response = tdjson::receive(2.0); 35 | if let Some(response_str) = response { 36 | let response: Value = serde_json::from_str(&response_str).unwrap(); 37 | 38 | match response.get("@extra") { 39 | Some(_) => { 40 | OBSERVER.notify(response); 41 | } 42 | None => { 43 | let client_id = response["@client_id"].as_i64().unwrap() as i32; 44 | match serde_json::from_value(response) { 45 | Ok(update) => { 46 | return Some((update, client_id)); 47 | } 48 | Err(e) => { 49 | log::warn!( 50 | "Received an unknown response: {}\nReason: {}", 51 | response_str, 52 | e 53 | ); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | None 61 | } 62 | 63 | pub(crate) async fn send_request(client_id: i32, mut request: Value) -> Value { 64 | let extra = EXTRA_COUNTER.fetch_add(1, Ordering::Relaxed); 65 | request["@extra"] = serde_json::to_value(extra).unwrap(); 66 | 67 | let receiver = OBSERVER.subscribe(extra); 68 | tdjson::send(client_id, request.to_string()); 69 | 70 | receiver.await.unwrap() 71 | } 72 | -------------------------------------------------------------------------------- /tdlib/src/observer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - developers of the `tdlib-rs` project. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | use futures_channel::oneshot; 9 | use serde_json::Value; 10 | use std::collections::HashMap; 11 | use std::sync::RwLock; 12 | 13 | pub(super) struct Observer { 14 | requests: RwLock>>, 15 | } 16 | 17 | impl Observer { 18 | pub fn new() -> Self { 19 | Observer { 20 | requests: RwLock::default(), 21 | } 22 | } 23 | 24 | pub fn subscribe(&self, extra: u32) -> oneshot::Receiver { 25 | let (sender, receiver) = oneshot::channel(); 26 | self.requests.write().unwrap().insert(extra, sender); 27 | receiver 28 | } 29 | 30 | pub fn notify(&self, response: Value) { 31 | let extra = response["@extra"].as_u64().unwrap() as u32; 32 | match self.requests.write().unwrap().remove(&extra) { 33 | Some(sender) => { 34 | if sender.send(response).is_err() { 35 | log::warn!("Got a response of an unaccessible request"); 36 | } 37 | } 38 | None => { 39 | log::warn!("Got a response of an unknown request"); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tdlib/src/tdjson.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 - developers of the `tdlib-rs` project. 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | use std::ffi::{CStr, CString}; 9 | use std::os::raw::{c_char, c_double, c_int}; 10 | 11 | #[link(name = "tdjson")] 12 | extern "C" { 13 | fn td_create_client_id() -> c_int; 14 | fn td_send(client_id: c_int, request: *const c_char); 15 | fn td_receive(timeout: c_double) -> *const c_char; 16 | } 17 | 18 | pub(crate) fn create_client() -> i32 { 19 | unsafe { td_create_client_id() } 20 | } 21 | 22 | pub(crate) fn send(client_id: i32, request: String) { 23 | let cstring = CString::new(request).unwrap(); 24 | unsafe { td_send(client_id, cstring.as_ptr()) } 25 | } 26 | 27 | pub(crate) fn receive(timeout: f64) -> Option { 28 | unsafe { 29 | td_receive(timeout) 30 | .as_ref() 31 | .map(|response| CStr::from_ptr(response).to_string_lossy().into_owned()) 32 | } 33 | } 34 | --------------------------------------------------------------------------------