├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── asserts └── images │ ├── client.png │ ├── codec-join.png │ ├── codec-join2.png │ ├── codec.png │ ├── incoming.png │ ├── make-connection.png │ ├── make-transport.png │ ├── overview.png │ ├── server.png │ └── transport.png ├── chapter1 └── Chapter1.md ├── chapter2 ├── Chapter2.md └── mini-lust │ ├── Cargo.toml │ ├── src │ └── lib.rs │ └── thrift │ └── demo.thrift ├── chapter3 ├── Chapter3.md └── mini-lust │ ├── Cargo.toml │ ├── src │ ├── binary.rs │ ├── context.rs │ ├── errors.rs │ ├── lib.rs │ ├── message.rs │ └── protocol.rs │ └── thrift │ └── demo.thrift ├── chapter4 ├── Chapter4.md └── mini-lust │ ├── Cargo.toml │ ├── src │ ├── binary.rs │ ├── codec.rs │ ├── context.rs │ ├── errors.rs │ ├── lib.rs │ ├── message.rs │ └── protocol.rs │ └── thrift │ └── demo.thrift ├── chapter5 ├── Chapter5.md └── mini-lust │ ├── Cargo.toml │ ├── examples │ ├── client.rs │ └── server.rs │ ├── src │ ├── binary.rs │ ├── client.rs │ ├── codec.rs │ ├── connection.rs │ ├── context.rs │ ├── errors.rs │ ├── lib.rs │ ├── message.rs │ ├── protocol.rs │ ├── server.rs │ ├── transport.rs │ └── utils.rs │ └── thrift │ └── demo.thrift ├── chapter6 ├── Chapter6-0.md ├── Chapter6-1.md ├── Chapter6-2.md ├── Chapter6-3.md ├── demo-derive-macro │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── demo-example-app-generated │ ├── Cargo.toml │ └── src │ │ ├── client.rs │ │ ├── generated.rs │ │ └── server.rs ├── demo-example-app │ ├── Cargo.toml │ └── src │ │ ├── client.rs │ │ ├── generated.rs │ │ └── server.rs ├── demo-generator │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── thrift │ │ └── demo.thrift ├── mini-lust-generator │ ├── Cargo.toml │ ├── src │ │ ├── code_gen │ │ │ ├── basic.rs │ │ │ ├── definition.rs │ │ │ ├── document.rs │ │ │ ├── errors.rs │ │ │ ├── field.rs │ │ │ ├── functions.rs │ │ │ ├── header.rs │ │ │ ├── mod.rs │ │ │ └── types.rs │ │ ├── errors.rs │ │ ├── generator.rs │ │ ├── lib.rs │ │ └── node.rs │ └── thrift │ │ ├── base.thrift │ │ ├── demo.thrift │ │ └── demo_base.thrift ├── mini-lust-macros │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── fields.rs │ │ ├── lib.rs │ │ ├── receiver.rs │ │ └── types.rs ├── mini-lust │ ├── Cargo.toml │ ├── src │ │ ├── binary.rs │ │ ├── client.rs │ │ ├── codec.rs │ │ ├── connection.rs │ │ ├── context.rs │ │ ├── errors.rs │ │ ├── lib.rs │ │ ├── message.rs │ │ ├── protocol.rs │ │ ├── server.rs │ │ ├── transport.rs │ │ ├── types.rs │ │ └── utils.rs │ └── thrift │ │ └── demo.thrift └── thrift-parser │ ├── Cargo.toml │ ├── README.md │ ├── examples │ ├── int_constant.rs │ └── parse_document.rs │ ├── src │ ├── basic.rs │ ├── constant.rs │ ├── definition.rs │ ├── document.rs │ ├── field.rs │ ├── functions.rs │ ├── header.rs │ ├── lib.rs │ ├── types.rs │ └── utils.rs │ └── thrift │ └── demo.thrift ├── chapter7 └── Chapter7.md └── chapter8 └── Chapter8.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "chapter2/mini-lust", 4 | "chapter3/mini-lust", 5 | "chapter4/mini-lust", 6 | "chapter5/mini-lust", 7 | "chapter6/demo-generator", 8 | "chapter6/demo-example-app", 9 | "chapter6/demo-example-app-generated", 10 | "chapter6/demo-derive-macro", 11 | "chapter6/mini-lust-generator", 12 | "chapter6/mini-lust-macros", 13 | "chapter6/mini-lust", 14 | "chapter6/thrift-parser", 15 | ] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mini Lust 系列教程 2 | 3 | 好奇如何从零造出来一个 RPC 框架?本教程将带你一步一步写出来一个 Rust 版 Thrift RPC 框架。 4 | 5 | ## 教程说明 6 | 从第二章开始每章节都会附带代码。 7 | 这个代码是在上一章节的基础上写的,文档里一般会告诉你增加了哪些东西,但是如果你想详细地对比到底哪里变动了,可以自行 diff。 8 | 9 | 每章的代码会尽量通过测试保证代码是正确工作的,我们会通过 CI 来向你保证这一点。 10 | 11 | 12 | ## 教程结构 13 | 依次分几个章节: 14 | 1. 前言部分,RPC 相关概念介绍 15 | 2. Thrift IDL 介绍 16 | 3. 序列化/反序列化的抽象 17 | 4. Codec 和 Transport 抽象 18 | 5. 客户端和服务端实现 19 | 6. Thrift IDL 解析和代码生成 20 | 7. 基于 tower 的服务发现和负载均衡 21 | 8. 中间件支持 22 | 23 | ## Credit 24 | - [@dyxushuai](https://github.com/dyxushuai): Lust project creator and helped mini-lust project a lot 25 | - [@ihciah](https://github.com/ihciah): Mini-lust tutorial creator and Lust project developer 26 | - [@PureWhiteWu](https://github.com/PureWhiteWu): Lust project developer 27 | - [@LYF1999](https://github.com/LYF1999): Lust project developer 28 | - [@Millione](https://github.com/Millione): Lust project developer 29 | 30 | ## 进度 31 | - 1~6 章节代码基本写完,教程尚需优化,部分细节需优化 32 | - CI 配置待补齐 33 | -------------------------------------------------------------------------------- /asserts/images/client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/client.png -------------------------------------------------------------------------------- /asserts/images/codec-join.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/codec-join.png -------------------------------------------------------------------------------- /asserts/images/codec-join2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/codec-join2.png -------------------------------------------------------------------------------- /asserts/images/codec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/codec.png -------------------------------------------------------------------------------- /asserts/images/incoming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/incoming.png -------------------------------------------------------------------------------- /asserts/images/make-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/make-connection.png -------------------------------------------------------------------------------- /asserts/images/make-transport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/make-transport.png -------------------------------------------------------------------------------- /asserts/images/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/overview.png -------------------------------------------------------------------------------- /asserts/images/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/server.png -------------------------------------------------------------------------------- /asserts/images/transport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/asserts/images/transport.png -------------------------------------------------------------------------------- /chapter1/Chapter1.md: -------------------------------------------------------------------------------- 1 | # 开始设计一个 RPC 框架吧 2 | 本系列教程将带你使用 **Rust** 从零造一个 **Thrift** 框架。 3 | 4 | 指导框架设计的一些经验很大一部分是在开发 Lust 框架的过程中探索出来的,所以本教程框架取名为 mini-lust。 5 | 6 | 由于本框架的目标只聚焦在 Thrift 上,并且想在不牺牲性能和扩展性的前提下尽可能地简单,所以实现上会和 Lust 有较大不同。 7 | 8 | Lust 框架是一个开源的高性能 RPC 框架,支持 Thrift 和 gRPC。近期开源,敬请期待。 9 | 10 | ![](../asserts/images/overview.png) 11 | 12 | ## 0x00 前言 13 | 14 | > RPC 框架设计是怎么回事呢?RPC 框架的使用相信大家都很熟悉,但是设计 RPC 框架是怎么回事呢?下面就让小编带大家一起了解吧。 15 | 16 | RPC 框架,顾名思义,做的事情就是 Remote Procedure Call。 17 | 只要自定义 IDL 就可以使用其来约定通信方式,然后就可以轻松完成远程调用。 18 | 19 | 框架要做哪些事情? 20 | 1. IDL 解析和序列化、反序列化代码生成 21 | 2. 服务治理,如超时等 22 | 3. 在可复用的连接上收发消息并能将请求和响应配对返回给用户 23 | 24 | ## 0x01 设计目标 25 | Mini-lust 的目标是一个**简洁**、**低耦合**且**高效**的 Thrift RPC 框架。 26 | 27 | 我们会尽可能地在关键路径上使用静态分发,虽然这可能会让泛型约束看起来有点难写。 28 | 29 | 我们也会尽可能地利用成熟的组件,如 tokio、tower 和 thrift 官方代码。 30 | 31 | 我们不会提供太多的功能,这不是一个大而全的框架(需要的话出门右转 Lust),但是我们会提供一定的可扩展性。 32 | 33 | ## 0x02 环境搭建 34 | 如果你没有 Rust 开发环境,可以按照下面的文档配置一下。 35 | 相比 Cpp 开发环境搭建,Rust 的可以说十分开箱即用了。 36 | 37 | Clion 和 VSCode 都很方便配置。 38 | 39 | 推荐使用 [RsProxy](https://rsproxy.cn/) 来加速国内的 crates 拉取。 40 | -------------------------------------------------------------------------------- /chapter2/Chapter2.md: -------------------------------------------------------------------------------- 1 | # Thrift IDL 定义 2 | 3 | 本章主要包括 Thrift IDL 相关知识,并将带你手动写一些结构体和实现(后续章节里将使用过程宏生成这些代码)。 4 | 5 | ## 0x00 什么是 IDL 以及为什么需要 IDL 6 | IDL 是 RPC 的必要组成部分,是客户端和服务端对通信的数据结构和调用方法的约定。 7 | 8 | 举例来说,我们有一个结构体想要发送接收: 9 | ```rust 10 | struct User { 11 | name: String, 12 | password: String, 13 | } 14 | ``` 15 | 16 | 我们可以使用一个自定义的语言来描述这个定义: 17 | 18 | 这里 `struct` 和 `str` 都是自定义的类型。 19 | ``` 20 | struct User { 21 | 1: str, 22 | 2: str, 23 | } 24 | ``` 25 | 26 | 之前群里有人吐槽:golang 的 RPC 框架要用 IDL 生成代码好麻烦,还要跑专门的工具;为什么不基于 HTTP,用户自定义 json 结构体,收消息然后自行解析? 27 | 实际上 RPC 框架是封装了消息收发、结构体编解码、IDL->结构体生成,以及一些服务治理逻辑。 28 | 29 | 与 golang 不同之处在于,在 rust 中我们可以利用 build.rs 来基于 IDL 生成结构体和结构体实现,代码生成会在后面章节讲到。利用这一点,就可以做到: 30 | 1. 生成代码不会被 Git 管理,不会插入用户 src 文件夹 31 | 2. 框架便于更新升级,不会受制于用户未重新生成代码导致的问题(这一点在 golang 下尤为致命) 32 | 3. 代码生成全自动,不需要用户手动运行脚本 33 | 34 | ## 0x01 Thrift IDL 定义 35 | 下文主要是从官方文档抄来的。 36 | 37 | 使用 BNF 表示: 38 | - `+` 表示出现一次或多次 39 | - `*` 表示出现零次或多次 40 | - `|` 表示选择,第一个命中的会被选中 41 | 42 | ### 标识符 43 | ``` 44 | Identifier ::= ( Letter | '_' ) ( Letter | Digit | '.' | '_' )* 45 | Letter ::= ['A'-'Z'] | ['a'-'z'] 46 | Digit ::= ['0'-'9'] 47 | ``` 48 | 标识符定义为 以字母或下划线开头,由字母、数字、点或下划线组成。举例来说,`bytedance` 和 `_bytedance123` 都是标识符,但 `1user` 不是。 49 | 50 | ### 字面量 51 | ``` 52 | Literal ::= ('"' [^"]* '"') | ("'" [^']* "'") 53 | ``` 54 | 字面量由双引号或单引号包裹。例如 `"yyds"`就是一个字面量,字面量是不分类型的。 55 | 56 | ### 基础类型 57 | ``` 58 | BaseType ::= 'bool' | 'byte' | 'i8' | 'i16' | 'i32' | 'i64' | 'double' | 'string' | 'binary' | 'slist' 59 | ``` 60 | 基础类型有上述几种,含义基本看名字就知道(其中 slist 已 deprecated)。 61 | 62 | ### 容器类型 63 | ``` 64 | ContainerType ::= MapType | SetType | ListType 65 | MapType ::= 'map' CppType? '<' FieldType ',' FieldType '>' 66 | SetType ::= 'set' CppType? '<' FieldType '>' 67 | ListType ::= 'list' '<' FieldType '>' CppType? 68 | CppType ::= 'cpp_type' Literal 69 | 70 | FieldType ::= Identifier | BaseType | ContainerType 71 | ``` 72 | 73 | 其中 CppType 是用 `'cpp_type'` 声明的字面量(用法不详,应该是 fb 内部使用)。 74 | 75 | 容器类型包括 Map、Set 和 List。 76 | 77 | Map 定义例如 `map < string, string >`,其中 key 和 value 类型都是 FieldType, 78 | 可以是 Identifier、BaseType 和 ContainerType(也就是说,类型是可以嵌套的,map 也可以是 map 的 key)。 79 | Set 和 List 类似,区别在于 Set 存储不重复的无序元素而 List 允许重复且有序,在此不赘述。 80 | 81 | ### 常量类型 82 | ``` 83 | ConstValue ::= IntConstant | DoubleConstant | Literal | Identifier | ConstList | ConstMap 84 | IntConstant ::= ('+' | '-')? Digit+ 85 | DoubleConstant ::= ('+' | '-')? Digit* ('.' Digit+)? ( ('E' | 'e') IntConstant )? 86 | ConstList ::= '[' (ConstValue ListSeparator?)* ']' 87 | ConstMap ::= '{' (ConstValue ':' ConstValue ListSeparator?)* '}' 88 | ``` 89 | 常量可以是 Int、Double、List 和 Map,也可以是 Literal 和 Identifier。Double 可以带小数点,可以使用科学计数法。 90 | 91 | ### Enum、Union 和 Struct 92 | ``` 93 | Enum ::= 'enum' Identifier '{' (Identifier ('=' IntConstant)? ListSeparator?)* '}' 94 | Struct ::= 'struct' Identifier 'xsd_all'? '{' Field* '}' 95 | Union ::= 'union' Identifier 'xsd_all'? '{' Field* '}' 96 | Exception ::= 'exception' Identifier '{' Field* '}' 97 | ``` 98 | 可用于对结构体的定义。Enum 是枚举类型,Struct 是结构体,Union 类似 C 语言里的 Union 只包含一个 child,Exception 是自定义的错误信息描述。 99 | 100 | ### Function 101 | ``` 102 | Function ::= 'oneway'? FunctionType Identifier '(' Field* ')' Throws? ListSeparator? 103 | FunctionType ::= FieldType | 'void' 104 | Throws ::= 'throws' '(' Field* ')' 105 | 106 | Field ::= FieldID? FieldReq? FieldType Identifier ('=' ConstValue)? XsdFieldOptions ListSeparator? 107 | FieldReq ::= 'required' | 'optional' 108 | FieldID ::= IntConstant ':' 109 | ListSeparator ::= ',' | ';' 110 | Throws ::= 'throws' '(' Field* ')' 111 | ``` 112 | FunctionType 可以是 FieldType(前文提到过,包括 Identifier、BaseType 和 ContainerType)也可以是 `void`,它表示了 function 的返回值定义。 113 | 114 | Identifier 是 function 的名字。 115 | 116 | 括号里是参数列表,由多个 Field 描述。 117 | 118 | 每个 Field 由 FieldID FieldReq FieldType 描述(如 `1: required bool are_we_ready (=false);`、`i32 user_count,`)。 119 | 120 | Function 整体描述如:`void func1() throws (XXException)`、`i32 get_user_count(1: Paid paid);` 121 | 122 | ### Service 123 | ``` 124 | Service ::= 'service' Identifier ( 'extends' Identifier )? '{' Function* '}' 125 | ``` 126 | Service 是由多个 Function 构成的。 127 | 128 | ### Definition 129 | ``` 130 | Definition ::= Const | Typedef | Enum | Senum | Struct | Union | Exception | Service 131 | Document ::= Header* Definition* 132 | Header ::= Include | CppInclude | Namespace 133 | ``` 134 | Document 由 Header 和 Definition 组成,即 `Const | Typedef | Enum | Senum | Struct | Union | Exception | Service` 可在 IDL 中被顶层定义。 135 | 136 | 137 | ## 0x03 写一个 Thrift IDL 并翻译成 Rust 138 | 我们设想一个从数据库 select 用户的需求,可选地提供 user_id 或 user_name 或性别,返回多个 User。 139 | ``` 140 | namespace rust demo 141 | 142 | struct User { 143 | 1: required i32 id, 144 | 2: required string user_name, 145 | 3: required bool is_male, 146 | 147 | 10: optional map extra, 148 | } 149 | 150 | struct GetUserRequest { 151 | 1: i32 user_id, 152 | 2: string user_name, 153 | 3: bool is_male, 154 | } 155 | 156 | struct GetUserResponse { 157 | 1: required list users, 158 | } 159 | 160 | service ItemService { 161 | GetUserResponse GetUser (1: GetUserRequest req), 162 | } 163 | ``` 164 | 165 | 手动翻译一下 IDL: 166 | ```rust 167 | use std::collections::HashMap; 168 | 169 | #[derive(Debug, Clone, Default, PartialEq)] 170 | pub struct User { 171 | user_id: i32, 172 | user_name: String, 173 | is_male: bool, 174 | 175 | extra: Option>, 176 | } 177 | 178 | #[derive(Debug, Clone, Default, PartialEq)] 179 | pub struct GetUserRequest { 180 | user_id: i32, 181 | user_name: String, 182 | is_male: bool, 183 | } 184 | 185 | #[derive(Debug, Clone, Default, PartialEq)] 186 | pub struct GetUserResponse { 187 | users: Vec, 188 | } 189 | ``` 190 | 注:只生成了基本结构体,Service 未生成。 191 | 192 | Thrift 的基本结构在 Rust 中往往有相对应的东西,可以直接映射一下。 193 | 194 | 另外,Thrift 的 Map 转换为了 rust 中的 HashMap 其实是有一定问题的,因为 Map 也可以作为另一个 Map 的 Key,而 HashMap 本身是不可 Hash 的。 195 | 这个问题我们暂时搁置,因为当前并没有将 Map 作为 Key 使用。这个在后续代码生成时可以考虑两个方案: 196 | 1. 全部使用 BTreeMap 197 | 2. 判断是否作为 Key 使用,如未作为 Key 则生成 HashMap,否则使用 BTreeMap 198 | 199 | ## 参考链接 200 | - Thrift IDL 定义: https://thrift.apache.org/docs/idl.html 201 | - 一些额外的定义解释: https://erikvanoosten.github.io/thrift-missing-specification/ 202 | -------------------------------------------------------------------------------- /chapter2/mini-lust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mini-lust-chap2" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /chapter2/mini-lust/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Debug, Clone, Default, PartialEq)] 4 | pub struct User { 5 | user_id: i32, 6 | user_name: String, 7 | is_male: bool, 8 | 9 | extra: Option>, 10 | } 11 | 12 | #[derive(Debug, Clone, Default, PartialEq)] 13 | pub struct GetUserRequest { 14 | user_id: i32, 15 | user_name: String, 16 | is_male: bool, 17 | } 18 | 19 | #[derive(Debug, Clone, Default, PartialEq)] 20 | pub struct GetUserResponse { 21 | users: Vec, 22 | } 23 | -------------------------------------------------------------------------------- /chapter2/mini-lust/thrift/demo.thrift: -------------------------------------------------------------------------------- 1 | namespace rust demo 2 | 3 | struct User { 4 | 1: required i32 user_id, 5 | 2: required string user_name, 6 | 3: required bool is_male, 7 | 8 | 10: optional map extra, 9 | } 10 | 11 | struct GetUserRequest { 12 | 1: i32 user_id, 13 | 2: string user_name, 14 | 3: bool is_male, 15 | } 16 | 17 | struct GetUserResponse { 18 | 1: required list users, 19 | } 20 | 21 | service ItemService { 22 | GetUserResponse GetUser (1: GetUserRequest req, 2: bool shuffle), 23 | } -------------------------------------------------------------------------------- /chapter3/Chapter3.md: -------------------------------------------------------------------------------- 1 | # 序列化和反序列化 2 | 3 | 本章主要内容是 Message trait 和 Protocol 定义,以及 Message trait 的实现。 4 | 5 | ## 0x00 Thrift 协议编码 6 | 7 | ### 0x00.0 消息体编解码 8 | 以下以 GetUserRequest 为例(参考 `lib.rs`)。 9 | 10 | 其 IDL 定义是: 11 | ``` 12 | struct GetUserRequest { 13 | 1: i32 user_id, 14 | 2: string user_name, 15 | 3: bool is_male, 16 | } 17 | ``` 18 | 19 | 我们为其写的对应序列化函数: 20 | ```rust 21 | protocol.write_struct_begin(&TStructIdentifier { 22 | name: "GetUserRequest".to_string(), 23 | })?; 24 | 25 | // user_id 26 | protocol.write_field_begin(&TFieldIdentifier { 27 | name: Some("user_id".to_string()), 28 | field_type: TType::I32, 29 | id: Some(1), 30 | })?; 31 | protocol.write_i32(self.user_id)?; 32 | protocol.write_field_end()?; 33 | 34 | // user_name 35 | protocol.write_field_begin(&TFieldIdentifier { 36 | name: Some("user_name".to_string()), 37 | field_type: TType::String, 38 | id: Some(2), 39 | })?; 40 | protocol.write_string(&self.user_name)?; 41 | protocol.write_field_end()?; 42 | 43 | // is_male 44 | protocol.write_field_begin(&TFieldIdentifier { 45 | name: Some("is_male".to_string()), 46 | field_type: TType::Bool, 47 | id: Some(3), 48 | })?; 49 | protocol.write_bool(self.is_male)?; 50 | protocol.write_field_end()?; 51 | 52 | protocol.write_field_stop()?; 53 | protocol.write_struct_end()?; 54 | Ok(()) 55 | ``` 56 | 57 | 可以看出,通常我们需要写一下字段头来标明后续数据的类型和长度,再做后续读写数据的流程。 58 | 并且在数据写入结束时要写入结束标记。 59 | 60 | 我们可以对照着看一下序列化的数据: 61 | ```rust 62 | GetUserRequest { 63 | user_id: 7, 64 | user_name: "ChiHai".to_string(), 65 | is_male: true, 66 | } 67 | 68 | // \x06\x00\x01\x00\x00\x00\x07\x08\x00\x02\x00\x00\x00\x06\x43\x68 69 | // \x69\x48\x61\x69\x02\x00\x03\x01\x00 70 | ``` 71 | 72 | 1. write_struct_begin: 在 Binary Protocol 实现中其实什么都没做。 73 | 2. write_field_begin: 74 | a. 先写 1byte 的 field type,对应 I32 是 `\x06`。 75 | b. 再写 i16 类型(2byte)的 field id,在我们 case 中是 1,即 `\x00\x01`。 76 | 3. write_i32: 写入 4 字节的 i32 数据 7,对应`\x00\x00\x00\x07`。 77 | 4. write_field_end: 在 Binary Protocol 实现中其实什么都没做。 78 | 5. write_field_begin: field type String = `\x08`;field id i16(2) = `\x00\x02`。 79 | 6. write_string: 80 | a. 先写 string 长度 i32 6: `\x00\x00\x00\x06`。 81 | b. 写 string 内容 ChiHai: `\x43\x68\x69\x48\x61\x69`。 82 | 7. write_field_end: 在 Binary Protocol 实现中其实什么都没做。 83 | 8. write_field_begin: field type Bool = `\x02`;field id i16(3) = `\x00\x03`。 84 | 9. write_bool: true = `\x01`。 85 | 10. write_field_end: 啥也不做。 86 | 11. write_field_stop: `\x00`。 87 | 12: write_struct_end: 啥也不做。 88 | 89 | 90 | 解码时(参考 `lib.rs`): 91 | ```rust 92 | let mut output = Self::default(); 93 | protocol.read_struct_begin()?; 94 | 95 | loop { 96 | let ident = protocol.read_field_begin()?; 97 | if ident.field_type == TType::Stop { 98 | break; 99 | } 100 | match ident.id { 101 | Some(1) => { 102 | ttype_comparing(ident.field_type, TType::I32)?; 103 | 104 | // read i32 105 | let content = protocol.read_i32()?; 106 | output.user_id = content; 107 | } 108 | Some(2) => { 109 | ttype_comparing(ident.field_type, TType::String)?; 110 | 111 | // read string 112 | let content = protocol.read_string()?; 113 | output.user_name = content; 114 | } 115 | Some(3) => { 116 | ttype_comparing(ident.field_type, TType::Bool)?; 117 | 118 | // read bool 119 | let content = protocol.read_bool()?; 120 | output.is_male = content; 121 | } 122 | _ => { 123 | protocol.skip(ident.field_type)?; 124 | } 125 | } 126 | protocol.read_field_end()?; 127 | } 128 | 129 | protocol.read_struct_end()?; 130 | Ok(output) 131 | ``` 132 | 133 | ### 0x00.1 衍生协议头 134 | 主要说说 Framed 协议头,这个是用的比较多的。当然其他自定义的协议头都可以套。 135 | 136 | Framed 协议头是在 Thrift 消息的基础上,在整个消息的前面加 4 字节的长度(u32 小端)。 137 | 138 | 这个长度信息十分有用,比如要做一个网关代理,只需要双方约定并通过自定义消息头传递必要元信息,显然是不关心消息内容的。 139 | 如果没办法提前知道消息长度,只能去逐字段解析并跳过 field,这个是很大的无用 cost。 140 | 141 | ## 0x01 Message 和 Protocol 142 | 序列化 & 反序列化,顾名思义是将消息和二进制互相转换的过程。在 Rust 中我们如何优雅地抽象消息序列化过程呢? 143 | 144 | Thrift 规范里写明了如何定义 Message,但是这个仅仅是定义,不涉及序列化的细节,如并不指定一个 i32 要如何表示; 145 | 还有一个概念是 Protocol,Protocol 指定了如何具体将消息序列化 & 反序列化。 146 | 147 | 所以这里有两个概念,一个是 Message,对应了消息的定义;一个是 Protocol,对应了消息的序列化协议。 148 | 149 | 在 Thrift 中,Protocol 通常有 2 种:Binary 和 Compact。 150 | Binary 协议一般由长度+数据构成,Compact 协议在 Binary 的基础上加上了压缩,对于小数字较多的数据包压缩性能较好。 151 | 152 | 设想一下,如果要定义一个结构体(以 User 为例)的编解码方式(编解码方式可以有多种),可以怎么写? 153 | 1. 定义两个包装:`Binary` 和 `Compact`,并为 `Binary` 和 `Compact` 实现 encode/decode 方法 154 | 2. 将 Binary 和 Compact 实现为 Protocol,为 User 实现 `encode_with_protocol(p: P)` 和 `decode_with_protocol(p: P)` 155 | 156 | 显然,后者耦合性更低,将消息的编解码流程(这可以是递归的)和具体的 Protocol 解耦,消息(实现了 Message trait)在编解码时使用 Protocol 作为读写方式。 157 | 158 | 事实上,在 [官方](https://github.com/apache/thrift/blob/master/lib/rs/README.md) 的实现中也都是使用的类似的 Protocol 抽象。 159 | 160 | ### 流式解码 & 非流式解码 161 | 在官方版本中,Protocol 的实现封装了底层 Transport(Read+Write),可以直接操作连接上的 IO 实现流式解码,可以直接异步读写结构体。 162 | 163 | 这样做的好处是可以适配裸的 Thrift 协议,可以在不预先获知长度的情况下解码; 164 | 但是这样做最大的问题是性能不佳:如果数据陆陆续续地缓慢到来,则会多很多读写的系统调用。 165 | 一次性读取足够的数据再解码或者编码后一次性写入会减少 syscall 的次数。 166 | 167 | 由于在协议头里携带 Body 长度信息是一个十分常见且应当的做法(上文提到的 Framed 就是官方的一个版本),在很多场景下可以更高效(如转发场景下可以做到不解包), 168 | 本项目不支持裸的 Thrift 协议,至少需要 Framed 或其他能够确定消息长度的协议头。 169 | 170 | 所以在 Lust 和本项目中,Protocol 只持有 MutBuf 的引用,所有的读写全部是对其操作,而不是网络 IO。 171 | 172 | 最终我们的 Message Trait 定义(参考 `message.rs`): 173 | ```rust 174 | trait Message { 175 | fn encode( 176 | &self, 177 | cx: &MsgContext, 178 | protocol: &mut T, 179 | ) -> Result<(), Error>; 180 | 181 | fn decode( 182 | cx: &mut MsgContext, 183 | protocol: &mut T, 184 | ) -> Result; 185 | } 186 | 187 | pub trait TInputProtocol { 188 | type Error; 189 | /// Read the beginning of a Thrift message. 190 | fn read_message_begin(&mut self) -> Result; 191 | /// Read the end of a Thrift message. 192 | fn read_message_end(&mut self) -> Result<(), Self::Error>; 193 | /// Read the beginning of a Thrift struct. 194 | fn read_struct_begin(&mut self) -> Result, Self::Error>; 195 | /// Read the end of a Thrift struct. 196 | fn read_struct_end(&mut self) -> Result<(), Self::Error>; 197 | 198 | // more functions... 199 | } 200 | 201 | pub trait TOutputProtocol { 202 | type Error; 203 | 204 | /// Write the beginning of a Thrift message. 205 | fn write_message_begin(&mut self, identifier: &TMessageIdentifier) -> Result<(), Self::Error>; 206 | /// Write the end of a Thrift message. 207 | fn write_message_end(&mut self) -> Result<(), Self::Error>; 208 | /// Write the beginning of a Thrift struct. 209 | fn write_struct_begin(&mut self, identifier: &TStructIdentifier) -> Result<(), Self::Error>; 210 | /// Write the end of a Thrift struct. 211 | fn write_struct_end(&mut self) -> Result<(), Self::Error>; 212 | 213 | // more functions... 214 | } 215 | ``` 216 | Message 的 encode 和 decode 接收 protocol 作为参数,将序列化、反序列化的过程实现为对 protocol 的操作(protocol 内部持有 BufMut 的引用,负责将对其的操作转换为对 BufMut 二进制形式的读写)。 217 | 218 | 我们将为一些生成的结构体实现 Message。 219 | 220 | ## 0x02 实现 Binary Protocol 221 | 关于 Trait 定义不用自己写太多,全部 Copy 自官方代码(见本章代码的 `protocol.rs` 和 `error.rs`,不贴了)。 222 | 223 | 之后是实现 Binary Protocol。 224 | 首先从官方代码 fork 一下到 `binary.rs`,之后按照我们前面的说明修改,将流式实现改为持有 buffer 引用读写 BufMut。 225 | 226 | 然后是手写一些 IDL 生成结构体的实现。主要是为它们实现 Message trait,这部分主要在 `lib.rs` 里。 227 | 在后面章节中,手写的应该生成的代码也会全放在 `lib.rs` 里。 228 | 229 | (另外本代码里修了一个官方代码的 message header strict 判断的 bug,并向官方提了 [PR](https://github.com/apache/thrift/pull/2412) ) 230 | 231 | 至此,我们可以基于 Message 接口对 IDL 定义的结构体按 Binary Protocol 做序列化 & 反序列化,并且有测试能够验证我们的结果是正确的。 232 | 233 | ### 0x02.0 零拷贝优化 234 | 另外一个后续可以考虑优化的地方是:当读 String 的时候,我们会先调用 read_bytes 将数据拷贝至 `Vec`,然后再使用其构建 String(传递了所有权无拷贝)。 235 | 虽然构建 String 没有拷贝,但是 read_bytes 的时候我们做了一次拷贝。 236 | 237 | 一个优化方案是,我们将 BufMut 中的字符串部分切下并 freeze 得到 Buf(内部实现是 Arc,有所有权),然后使用它来构建 String(带所有权)并返回给用户。 238 | 当这个字符串 drop,这个 buffer 会回到池子中。但唯一的问题在于,原生的 String 内部是 Vec。 239 | 所以如果不用一些 unsafe 的骚操作(关于这部分[做了一下尝试](https://gist.github.com/ihciah/4e1d602891c22ddf82cdb81549ba9d41),感兴趣可以参考),我们就只能另外搞出一个类型来替代 String。但是这样使用起来就不太用户友好。 240 | 目前这个优化暂时搁置,后续可以另行考虑。 241 | 242 | ## 0x03 MsgContext 243 | 看到 Context 这个词你可能会想到 golang 里的 Context。没错,它在这里的设计用途其实和 golang 里你写的 context 是一样的。 244 | 245 | 因为我们框架内部在处理请求时,后面的处理会依赖于前面的结果,所以利用 Context 透传这些信息,就会使这个过程比较容易。 246 | 247 | 目前我们 MsgContext 中是空的,后续有需求会加入新的字段。 248 | 249 | 你可能想问:为什么非要在 encode 和 decode 接口上传递 MsgContext 参数? 250 | 这是因为我们想让 普通的结构体(类似 Protobuf 做的事情)和 RPC 请求(类似 gRPC 做的事情)的序列化和反序列化共用这个 Message 抽象,我们需要读写 RPC 元信息。 251 | 下一章里我们会详细说明。 252 | 253 | ## 参考链接 254 | Thrift 的一些概念: https://thrift.apache.org/docs/concepts.html 255 | -------------------------------------------------------------------------------- /chapter3/mini-lust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mini-lust-chap3" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | bytes = "1.0" 10 | byteorder = "1.4" 11 | num_enum = "0.5" 12 | -------------------------------------------------------------------------------- /chapter3/mini-lust/src/context.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default)] 2 | pub struct MsgContext { 3 | 4 | } -------------------------------------------------------------------------------- /chapter3/mini-lust/src/message.rs: -------------------------------------------------------------------------------- 1 | use crate::context::MsgContext; 2 | use crate::protocol::{TInputProtocol, TOutputProtocol}; 3 | 4 | pub trait Message: Sized { 5 | fn encode(&self, cx: &MsgContext, protocol: &mut T) -> crate::Result<()>; 6 | fn decode(cx: &mut MsgContext, protocol: &mut T) -> crate::Result; 7 | } 8 | -------------------------------------------------------------------------------- /chapter3/mini-lust/thrift/demo.thrift: -------------------------------------------------------------------------------- 1 | namespace rust demo 2 | 3 | struct User { 4 | 1: required i32 user_id, 5 | 2: required string user_name, 6 | 3: required bool is_male, 7 | 8 | 10: optional map extra, 9 | } 10 | 11 | struct GetUserRequest { 12 | 1: i32 user_id, 13 | 2: string user_name, 14 | 3: bool is_male, 15 | } 16 | 17 | struct GetUserResponse { 18 | 1: required list users, 19 | } 20 | 21 | service ItemService { 22 | GetUserResponse GetUser (1: GetUserRequest req, 2: bool shuffle), 23 | } -------------------------------------------------------------------------------- /chapter4/mini-lust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mini-lust-chap4" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | bytes = "1.0" 10 | byteorder = "1.4" 11 | num_enum = "0.5" 12 | 13 | tokio-util = { version = "0.6", features = ["codec"] } 14 | 15 | [dev-dependencies] 16 | tokio-test = "0.4" 17 | tokio = { version = "1", features = ["macros", "rt"] } 18 | futures-util = { version = "0.3", features = ["default", "sink"] } 19 | -------------------------------------------------------------------------------- /chapter4/mini-lust/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::protocol::TMessageIdentifier; 2 | 3 | #[derive(Debug, Default, Eq, PartialEq)] 4 | pub struct MsgContext { 5 | pub identifier: TMessageIdentifier, 6 | } 7 | -------------------------------------------------------------------------------- /chapter4/mini-lust/src/message.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use crate::context::MsgContext; 4 | use crate::protocol::{ 5 | TFieldIdentifier, TInputProtocol, TOutputProtocol, TStructIdentifier, TType, 6 | }; 7 | use crate::{new_application_error, ApplicationError, ApplicationErrorKind, Error}; 8 | 9 | pub trait Message: Sized { 10 | fn encode(&self, cx: &MsgContext, protocol: &mut T) -> crate::Result<()>; 11 | fn decode(cx: &mut MsgContext, protocol: &mut T) -> crate::Result; 12 | } 13 | 14 | /// ApplicationError defined as: 15 | /// exception TApplicationException { 16 | /// 1: string message, 17 | /// 2: i32 type 18 | /// } 19 | /// 20 | impl Message for ApplicationError { 21 | fn encode(&self, _cx: &MsgContext, protocol: &mut T) -> crate::Result<()> { 22 | protocol.write_struct_begin(&TStructIdentifier { 23 | name: "ApplicationError".to_string(), 24 | })?; 25 | protocol.write_field_begin(&TFieldIdentifier { 26 | name: Some("message".to_string()), 27 | field_type: TType::Struct, 28 | id: Some(1), 29 | })?; 30 | protocol.write_string(self.message.as_str())?; 31 | protocol.write_field_end()?; 32 | 33 | protocol.write_field_begin(&TFieldIdentifier { 34 | name: Some("type".to_string()), 35 | field_type: TType::I32, 36 | id: Some(2), 37 | })?; 38 | protocol.write_i32(self.kind.into())?; 39 | protocol.write_field_end()?; 40 | protocol.write_field_stop()?; 41 | protocol.write_struct_end()?; 42 | Ok(()) 43 | } 44 | 45 | fn decode(cx: &mut MsgContext, protocol: &mut T) -> crate::Result { 46 | protocol.read_struct_begin()?; 47 | let mut output = Self::default(); 48 | 49 | loop { 50 | let ident = protocol.read_field_begin()?; 51 | if ident.field_type == TType::Stop { 52 | break; 53 | } 54 | match ident.id { 55 | Some(1) => { 56 | // read string 57 | output.message = String::decode(cx, protocol)?; 58 | } 59 | Some(2) => { 60 | // read i32 61 | output.kind = protocol.read_i32()?.try_into()?; 62 | } 63 | _ => { 64 | protocol.skip(ident.field_type)?; 65 | } 66 | } 67 | protocol.read_field_end()?; 68 | } 69 | protocol.read_struct_end()?; 70 | Ok(output) 71 | } 72 | } 73 | 74 | impl Message for Error { 75 | fn encode(&self, _cx: &MsgContext, _protocol: &mut T) -> crate::Result<()> { 76 | // TODO: encode error 77 | Ok(()) 78 | } 79 | 80 | fn decode(_cx: &mut MsgContext, _protocol: &mut T) -> crate::Result { 81 | // TODO: decode error 82 | Ok(new_application_error(ApplicationErrorKind::Unknown, "mock")) 83 | } 84 | } 85 | 86 | macro_rules! impl_message { 87 | ($e: ty, $r: ident, $w: ident) => { 88 | impl Message for $e { 89 | fn encode( 90 | &self, 91 | _cx: &MsgContext, 92 | protocol: &mut T, 93 | ) -> crate::Result<()> { 94 | protocol.$w(self)?; 95 | Ok(()) 96 | } 97 | 98 | fn decode( 99 | _cx: &mut MsgContext, 100 | protocol: &mut T, 101 | ) -> crate::Result { 102 | protocol.$r() 103 | } 104 | } 105 | }; 106 | } 107 | 108 | macro_rules! impl_message_deref { 109 | ($e: ty, $r: ident, $w: ident) => { 110 | impl Message for $e { 111 | fn encode( 112 | &self, 113 | _cx: &MsgContext, 114 | protocol: &mut T, 115 | ) -> crate::Result<()> { 116 | protocol.$w(*self)?; 117 | Ok(()) 118 | } 119 | 120 | fn decode( 121 | _cx: &mut MsgContext, 122 | protocol: &mut T, 123 | ) -> crate::Result { 124 | protocol.$r() 125 | } 126 | } 127 | }; 128 | } 129 | 130 | impl_message_deref!(bool, read_bool, write_bool); 131 | impl_message_deref!(i8, read_i8, write_i8); 132 | impl_message_deref!(i16, read_i16, write_i16); 133 | impl_message_deref!(i32, read_i32, write_i32); 134 | impl_message_deref!(i64, read_i64, write_i64); 135 | impl_message!(String, read_string, write_string); 136 | -------------------------------------------------------------------------------- /chapter4/mini-lust/thrift/demo.thrift: -------------------------------------------------------------------------------- 1 | namespace rust demo 2 | 3 | struct User { 4 | 1: required i32 user_id, 5 | 2: required string user_name, 6 | 3: required bool is_male, 7 | 8 | 10: optional map extra, 9 | } 10 | 11 | struct GetUserRequest { 12 | 1: i32 user_id, 13 | 2: string user_name, 14 | 3: bool is_male, 15 | } 16 | 17 | struct GetUserResponse { 18 | 1: required list users, 19 | } 20 | 21 | service ItemService { 22 | GetUserResponse GetUser (1: GetUserRequest req, 2: bool shuffle), 23 | } -------------------------------------------------------------------------------- /chapter5/mini-lust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mini-lust-chap5" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | log = "0.4" 10 | bytes = "1.0" 11 | byteorder = "1.4" 12 | num_enum = "0.5" 13 | async-trait = "0.1" 14 | thiserror = "1.0" 15 | pin-project = "1.0" 16 | 17 | tokio-tower = "0.5" 18 | tokio-stream = { version = "0.1", features = ["net"] } 19 | tokio-util = { version = "0.6", features = ["codec"] } 20 | tower = { version = "0.4", features = ["make", "balance", "discover", "util", "limit", "buffer"] } 21 | futures-core = "0.3" 22 | futures = { version = "0.3", features = ["async-await", "std"] } 23 | futures-util = { version = "0.3", features = ["default", "sink"] } 24 | tokio = { version = "1", features = ["macros", "rt", "net", "rt-multi-thread"] } 25 | 26 | [dev-dependencies] 27 | tokio-test = "0.4" 28 | env_logger = "0.8" 29 | 30 | [features] 31 | default = [] 32 | unstable = [] 33 | 34 | [[example]] 35 | name = "client" 36 | crate-type = ["bin"] 37 | 38 | [[example]] 39 | name = "server" 40 | crate-type = ["bin"] 41 | -------------------------------------------------------------------------------- /chapter5/mini-lust/examples/client.rs: -------------------------------------------------------------------------------- 1 | use mini_lust_chap5::{GetUserRequest, SocketOrUnix, ItemServiceClientBuilder}; 2 | 3 | #[tokio::main] 4 | async fn main() { 5 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 6 | 7 | let target = SocketOrUnix::Socket("127.0.0.1:12345".parse().unwrap()); 8 | let mut client = ItemServiceClientBuilder::new(target).build(); 9 | 10 | let resp = client 11 | .get_user( 12 | GetUserRequest { 13 | user_id: 1, 14 | user_name: "ihciah".to_string(), 15 | is_male: false, 16 | }, 17 | true, 18 | ) 19 | .await 20 | .unwrap(); 21 | log::info!("{:?}", resp); 22 | } 23 | -------------------------------------------------------------------------------- /chapter5/mini-lust/examples/server.rs: -------------------------------------------------------------------------------- 1 | use mini_lust_chap5::{ItemService, GetUserRequest, AnonymousItemServiceGetUserResult, ApplicationResult, GetUserResponse, User, ItemServiceServer, Server}; 2 | use std::net::SocketAddr; 3 | 4 | struct Svc; 5 | 6 | #[async_trait::async_trait] 7 | impl ItemService for Svc { 8 | async fn get_user( 9 | &self, 10 | req: GetUserRequest, 11 | shuffle: bool, 12 | ) -> ApplicationResult { 13 | log::info!("receive a get_user request: req = {:?}, shuffle = {:?}", req, shuffle); 14 | 15 | let mut resp = GetUserResponse::default(); 16 | resp.users.push(User { 17 | user_id: req.user_id, 18 | user_name: req.user_name, 19 | is_male: shuffle, 20 | extra: None, 21 | }); 22 | Ok(AnonymousItemServiceGetUserResult::Success(resp)) 23 | } 24 | } 25 | 26 | #[tokio::main] 27 | async fn main() { 28 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 29 | 30 | let server = Server::new(ItemServiceServer::new(Svc)); 31 | let addr = "127.0.0.1:12345".parse::().unwrap(); 32 | 33 | log::info!("Will serve on 127.0.0.1:12345"); 34 | let _ = server.serve(addr).await; 35 | } -------------------------------------------------------------------------------- /chapter5/mini-lust/src/client.rs: -------------------------------------------------------------------------------- 1 | use std::task::{Context, Poll}; 2 | 3 | use futures::sink::SinkExt; 4 | use futures::stream::TryStreamExt; 5 | use tokio::io::{AsyncRead, AsyncWrite}; 6 | use tower::buffer::{Buffer, BufferLayer}; 7 | use tower::util::BoxService; 8 | use tower::{Layer, Service, ServiceExt}; 9 | 10 | use crate::codec::MakeCodec; 11 | use crate::connection::SocketOrUnix; 12 | use crate::context::MsgContext; 13 | use crate::protocol::{TMessageIdentifier, TMessageType}; 14 | use crate::utils::BoxFuture; 15 | use crate::{ApplicationResult, DefaultMakeCodec, DefaultMakeConnection, FramedMakeTransport}; 16 | 17 | pub struct ClientBuilder { 18 | target: SocketOrUnix, 19 | make_codec: MCC, 20 | } 21 | 22 | impl ClientBuilder> { 23 | pub fn new(target: SocketOrUnix) -> Self { 24 | Self { 25 | target, 26 | make_codec: DefaultMakeCodec::new(), 27 | } 28 | } 29 | } 30 | 31 | impl ClientBuilder { 32 | pub fn make_codec(self, make_codec: MCC) -> Self { 33 | Self { 34 | target: self.target, 35 | make_codec, 36 | } 37 | } 38 | } 39 | 40 | const DEFAULT_BUFFER: usize = usize::MAX >> 3; 41 | 42 | impl ClientBuilder 43 | where 44 | Req: Send + 'static, 45 | Resp: Send + 'static, 46 | MCC: MakeCodec< 47 | EncodeItem = (MsgContext, ApplicationResult), 48 | DecodeItem = (MsgContext, ApplicationResult), 49 | Error = crate::Error, 50 | > + Send 51 | + 'static, 52 | MCC::Codec: Send + 'static, 53 | { 54 | pub fn build(self) -> Client { 55 | let make_connection = DefaultMakeConnection; 56 | let make_codec = self.make_codec; 57 | let transport_client = TransportClient::new(make_connection, make_codec); 58 | let inner = Buffer::new(BoxService::new(transport_client), DEFAULT_BUFFER); 59 | Client { 60 | inner, 61 | target: self.target, 62 | } 63 | } 64 | } 65 | 66 | #[derive(Clone)] 67 | pub struct Client { 68 | inner: Buffer< 69 | BoxService< 70 | (MsgContext, ApplicationResult), 71 | Option<(MsgContext, ApplicationResult)>, 72 | crate::Error, 73 | >, 74 | (MsgContext, ApplicationResult), 75 | >, 76 | target: SocketOrUnix, 77 | } 78 | 79 | impl Client { 80 | /// Call with method and Req and returns Result 81 | pub async fn call(&mut self, method: &'static str, req: Req) -> crate::Result { 82 | let context = MsgContext { 83 | identifier: TMessageIdentifier { 84 | name: method.to_string(), 85 | message_type: TMessageType::Call, 86 | 87 | ..TMessageIdentifier::default() 88 | }, 89 | target: Some(self.target.clone()), 90 | }; 91 | let req = (context, Ok(req)); 92 | // Option<(MsgContext, ApplicationResult)> 93 | let resp = self.inner.ready().await?.call(req).await?; 94 | resp.expect("returning resp is expected") 95 | .1 96 | .map_err(Into::into) 97 | } 98 | 99 | pub async fn oneway(&mut self, method: &'static str, req: Req) -> crate::Result<()> { 100 | let context = MsgContext { 101 | identifier: TMessageIdentifier { 102 | name: method.to_string(), 103 | message_type: TMessageType::OneWay, 104 | 105 | ..TMessageIdentifier::default() 106 | }, 107 | target: Some(self.target.clone()), 108 | }; 109 | let req = (context, Ok(req)); 110 | self.inner.ready().await?.call(req).await?; 111 | Ok(()) 112 | } 113 | } 114 | 115 | pub(crate) struct TransportClient { 116 | make_transport: FramedMakeTransport, 117 | seq_id: i32, 118 | } 119 | 120 | impl TransportClient { 121 | #[allow(unused)] 122 | pub fn new(make_connection: MCN, make_codec: MCC) -> Self { 123 | Self { 124 | make_transport: FramedMakeTransport::new(make_connection, make_codec), 125 | seq_id: 1, 126 | } 127 | } 128 | } 129 | 130 | /// Call with (MsgContext, ApplicationResult) returns 131 | /// Option<(MsgContext, ApplicationResult)>. 132 | impl Service<(MsgContext, ApplicationResult)> 133 | for TransportClient 134 | where 135 | MCN: Service + Clone + Send + 'static, 136 | MCN::Response: AsyncRead + AsyncWrite + Unpin + Send, 137 | MCN::Error: std::error::Error + Send + Sync + 'static, 138 | MCN::Future: Send, 139 | Req: Send + 'static, 140 | Resp: Send, 141 | MCC: MakeCodec< 142 | EncodeItem = (MsgContext, ApplicationResult), 143 | DecodeItem = (MsgContext, ApplicationResult), 144 | Error = crate::Error, 145 | >, 146 | MCC::Codec: Send + 'static, 147 | crate::Error: From<>::Error>, 148 | { 149 | // If oneway, the Response is None 150 | type Response = Option<(MsgContext, ApplicationResult)>; 151 | type Error = crate::Error; 152 | type Future = BoxFuture; 153 | 154 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 155 | self.make_transport.poll_ready(cx).map_err(Into::into) 156 | } 157 | 158 | fn call(&mut self, (mut cx, req): (MsgContext, ApplicationResult)) -> Self::Future { 159 | // TODO: use service discovery 160 | let target = cx 161 | .target 162 | .clone() 163 | .expect("unable to retrieve target from context"); 164 | let transport_fut = self.make_transport.call(target); 165 | 166 | self.seq_id += 1; 167 | cx.identifier.sequence_number = self.seq_id; 168 | let oneway = cx.identifier.message_type == TMessageType::OneWay; 169 | Box::pin(async move { 170 | let mut transport = transport_fut.await?; 171 | transport.send((cx, req)).await?; 172 | if oneway { 173 | return Ok(None); 174 | } 175 | transport.try_next().await.map_err(Into::into) 176 | }) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /chapter5/mini-lust/src/connection.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::SocketAddr; 3 | use std::path::PathBuf; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | 7 | use futures_util::FutureExt; 8 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 9 | use tower::Service; 10 | 11 | use crate::utils::BoxFuture; 12 | 13 | pub trait Io: AsyncWrite + AsyncRead + Send + 'static {} 14 | 15 | impl Io for T where T: AsyncRead + AsyncWrite + Send + 'static {} 16 | 17 | pub struct BoxedIo(Pin>); 18 | 19 | impl BoxedIo { 20 | pub fn new(io: I) -> BoxedIo { 21 | BoxedIo(Box::pin(io)) 22 | } 23 | } 24 | 25 | impl AsyncRead for BoxedIo { 26 | fn poll_read( 27 | mut self: Pin<&mut Self>, 28 | cx: &mut Context<'_>, 29 | buf: &mut ReadBuf<'_>, 30 | ) -> Poll> { 31 | Pin::new(&mut self.0).poll_read(cx, buf) 32 | } 33 | } 34 | 35 | impl AsyncWrite for BoxedIo { 36 | fn poll_write( 37 | mut self: Pin<&mut Self>, 38 | cx: &mut Context<'_>, 39 | buf: &[u8], 40 | ) -> Poll> { 41 | Pin::new(&mut self.0).poll_write(cx, buf) 42 | } 43 | 44 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 45 | Pin::new(&mut self.0).poll_flush(cx) 46 | } 47 | 48 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 49 | Pin::new(&mut self.0).poll_shutdown(cx) 50 | } 51 | } 52 | 53 | #[derive(Debug, Clone)] 54 | pub struct DefaultMakeConnection; 55 | 56 | #[derive(Debug, Clone, Eq, PartialEq)] 57 | pub enum SocketOrUnix { 58 | Socket(SocketAddr), 59 | #[cfg(unix)] 60 | Unix(PathBuf), 61 | } 62 | 63 | impl Service for DefaultMakeConnection { 64 | type Response = tokio::net::TcpStream; 65 | type Error = std::io::Error; 66 | type Future = BoxFuture; 67 | 68 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 69 | Poll::Ready(Ok(())) 70 | } 71 | 72 | fn call(&mut self, req: SocketAddr) -> Self::Future { 73 | Box::pin(tokio::net::TcpStream::connect(req)) 74 | } 75 | } 76 | 77 | #[cfg(unix)] 78 | impl Service for DefaultMakeConnection { 79 | type Response = tokio::net::UnixStream; 80 | type Error = std::io::Error; 81 | type Future = BoxFuture; 82 | 83 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 84 | Poll::Ready(Ok(())) 85 | } 86 | 87 | fn call(&mut self, req: PathBuf) -> Self::Future { 88 | Box::pin(tokio::net::UnixStream::connect(req)) 89 | } 90 | } 91 | 92 | impl Service for DefaultMakeConnection { 93 | type Response = BoxedIo; 94 | type Error = std::io::Error; 95 | type Future = BoxFuture; 96 | 97 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 98 | Poll::Ready(Ok(())) 99 | } 100 | 101 | fn call(&mut self, req: SocketOrUnix) -> Self::Future { 102 | match req { 103 | SocketOrUnix::Socket(addr) => { 104 | Box::pin(FutureExt::map(tokio::net::TcpStream::connect(addr), |r| { 105 | r.map(BoxedIo::new) 106 | })) 107 | } 108 | #[cfg(unix)] 109 | SocketOrUnix::Unix(path) => { 110 | Box::pin(FutureExt::map(tokio::net::UnixStream::connect(path), |r| { 111 | r.map(BoxedIo::new) 112 | })) 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /chapter5/mini-lust/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::protocol::TMessageIdentifier; 2 | use crate::connection::SocketOrUnix; 3 | 4 | /// MsgContext can only be used across our framework and middleware. 5 | #[derive(Clone, Debug, Default, Eq, PartialEq)] 6 | pub struct MsgContext { 7 | /// Thrift TMessageIdentifier 8 | pub identifier: TMessageIdentifier, 9 | /// target 10 | pub target: Option, 11 | } 12 | -------------------------------------------------------------------------------- /chapter5/mini-lust/src/message.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use crate::context::MsgContext; 4 | use crate::protocol::{ 5 | TFieldIdentifier, TInputProtocol, TOutputProtocol, TStructIdentifier, TType, 6 | }; 7 | use crate::ApplicationError; 8 | 9 | pub trait Message: Sized { 10 | fn encode(&self, cx: &MsgContext, protocol: &mut T) -> crate::Result<()>; 11 | fn decode(cx: &mut MsgContext, protocol: &mut T) -> crate::Result; 12 | } 13 | 14 | /// ApplicationError defined as: 15 | /// exception TApplicationException { 16 | /// 1: string message, 17 | /// 2: i32 type 18 | /// } 19 | /// 20 | impl Message for ApplicationError { 21 | fn encode(&self, _cx: &MsgContext, protocol: &mut T) -> crate::Result<()> { 22 | protocol.write_struct_begin(&TStructIdentifier { 23 | name: "ApplicationError".to_string(), 24 | })?; 25 | protocol.write_field_begin(&TFieldIdentifier { 26 | name: Some("message".to_string()), 27 | field_type: TType::Struct, 28 | id: Some(1), 29 | })?; 30 | protocol.write_string(self.message.as_str())?; 31 | protocol.write_field_end()?; 32 | 33 | protocol.write_field_begin(&TFieldIdentifier { 34 | name: Some("type".to_string()), 35 | field_type: TType::I32, 36 | id: Some(2), 37 | })?; 38 | protocol.write_i32(self.kind.into())?; 39 | protocol.write_field_end()?; 40 | protocol.write_field_stop()?; 41 | protocol.write_struct_end()?; 42 | Ok(()) 43 | } 44 | 45 | fn decode(cx: &mut MsgContext, protocol: &mut T) -> crate::Result { 46 | protocol.read_struct_begin()?; 47 | let mut output = Self::default(); 48 | 49 | loop { 50 | let ident = protocol.read_field_begin()?; 51 | if ident.field_type == TType::Stop { 52 | break; 53 | } 54 | match ident.id { 55 | Some(1) => { 56 | // read string 57 | output.message = String::decode(cx, protocol)?; 58 | } 59 | Some(2) => { 60 | // read i32 61 | output.kind = protocol.read_i32()?.try_into()?; 62 | } 63 | _ => { 64 | protocol.skip(ident.field_type)?; 65 | } 66 | } 67 | protocol.read_field_end()?; 68 | } 69 | protocol.read_struct_end()?; 70 | Ok(output) 71 | } 72 | } 73 | 74 | macro_rules! impl_message { 75 | ($e: ty, $r: ident, $w: ident) => { 76 | impl Message for $e { 77 | fn encode( 78 | &self, 79 | _cx: &MsgContext, 80 | protocol: &mut T, 81 | ) -> crate::Result<()> { 82 | protocol.$w(self)?; 83 | Ok(()) 84 | } 85 | 86 | fn decode( 87 | _cx: &mut MsgContext, 88 | protocol: &mut T, 89 | ) -> crate::Result { 90 | protocol.$r() 91 | } 92 | } 93 | }; 94 | } 95 | 96 | macro_rules! impl_message_deref { 97 | ($e: ty, $r: ident, $w: ident) => { 98 | impl Message for $e { 99 | fn encode( 100 | &self, 101 | _cx: &MsgContext, 102 | protocol: &mut T, 103 | ) -> crate::Result<()> { 104 | protocol.$w(*self)?; 105 | Ok(()) 106 | } 107 | 108 | fn decode( 109 | _cx: &mut MsgContext, 110 | protocol: &mut T, 111 | ) -> crate::Result { 112 | protocol.$r() 113 | } 114 | } 115 | }; 116 | } 117 | 118 | impl_message_deref!(bool, read_bool, write_bool); 119 | impl_message_deref!(i8, read_i8, write_i8); 120 | impl_message_deref!(i16, read_i16, write_i16); 121 | impl_message_deref!(i32, read_i32, write_i32); 122 | impl_message_deref!(i64, read_i64, write_i64); 123 | impl_message!(String, read_string, write_string); 124 | -------------------------------------------------------------------------------- /chapter5/mini-lust/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::marker::PhantomData; 3 | use std::net::SocketAddr; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | 7 | use futures::SinkExt; 8 | use futures_core::{ 9 | ready, 10 | stream::{Stream, TryStream}, 11 | }; 12 | use tokio::io::{AsyncRead, AsyncWrite}; 13 | use tokio::net::TcpStream; 14 | use tokio_stream::wrappers::TcpListenerStream; 15 | use tokio_stream::StreamExt; 16 | use tokio_util::codec::Framed; 17 | use tower::{Service, ServiceBuilder, ServiceExt}; 18 | 19 | use crate::codec::{DefaultMakeCodec, MakeCodec}; 20 | use crate::context::MsgContext; 21 | use crate::message::Message; 22 | use crate::protocol::TMessageType; 23 | use crate::{ApplicationError, ApplicationErrorKind, ApplicationResult}; 24 | 25 | #[async_trait::async_trait] 26 | pub trait Listenable { 27 | type Conn: AsyncRead + AsyncWrite + Send + Unpin + 'static; 28 | type Stream: Stream> + Unpin; 29 | 30 | async fn bind(&self) -> io::Result; 31 | } 32 | 33 | #[async_trait::async_trait] 34 | impl Listenable for SocketAddr { 35 | type Conn = TcpStream; 36 | type Stream = TcpListenerStream; 37 | 38 | async fn bind(&self) -> io::Result { 39 | let listener = tokio::net::TcpListener::bind(self).await?; 40 | Ok(TcpListenerStream::new(listener)) 41 | } 42 | } 43 | 44 | #[cfg(unix)] 45 | #[async_trait::async_trait] 46 | impl Listenable for std::path::PathBuf { 47 | type Conn = tokio::net::UnixStream; 48 | type Stream = tokio_stream::wrappers::UnixListenerStream; 49 | 50 | async fn bind(&self) -> io::Result { 51 | let listener = tokio::net::UnixListener::bind(self)?; 52 | Ok(tokio_stream::wrappers::UnixListenerStream::new(listener)) 53 | } 54 | } 55 | 56 | #[pin_project::pin_project] 57 | pub struct Incoming { 58 | #[pin] 59 | listener_stream: LS, 60 | make_codec: MC, 61 | } 62 | 63 | impl Incoming { 64 | #[allow(unused)] 65 | pub fn new(listener_stream: LS, make_codec: MC) -> Self { 66 | Self { 67 | listener_stream, 68 | make_codec, 69 | } 70 | } 71 | } 72 | 73 | impl Stream for Incoming 74 | where 75 | LS: TryStream, 76 | LS::Ok: AsyncRead + AsyncWrite, 77 | MC: MakeCodec, 78 | { 79 | type Item = Result, LS::Error>; 80 | 81 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 82 | let this = self.project(); 83 | let mut listener_stream = this.listener_stream; 84 | let codec = this.make_codec; 85 | match ready!(listener_stream.as_mut().try_poll_next(cx)) { 86 | Some(Ok(conn)) => { 87 | let f = Framed::new(conn, codec.make_codec()); 88 | Poll::Ready(Some(Ok(f))) 89 | } 90 | Some(Err(e)) => Poll::Ready(Some(Err(e))), 91 | None => Poll::Ready(None), 92 | } 93 | } 94 | } 95 | 96 | #[derive(thiserror::Error, Debug)] 97 | pub enum ServerError { 98 | #[error("IO error")] 99 | IO(#[from] io::Error), 100 | } 101 | 102 | const DEFAULT_BUFFER: usize = 1e3 as usize; // FIXME 103 | const DEFAULT_CONCURRENCY_LIMIT: usize = 1e3 as usize; // FIXME 104 | 105 | pub struct Server { 106 | concurrency_limit: Option, 107 | buffer: Option, 108 | inner: S, 109 | _marker: PhantomData, 110 | } 111 | 112 | impl Server 113 | where 114 | S: Service< 115 | (MsgContext, ApplicationResult), 116 | Response = Option<(MsgContext, ApplicationResult)>, 117 | Error = crate::Error, 118 | > + Send 119 | + 'static, 120 | S::Future: Send, 121 | { 122 | pub fn new(inner: S) -> Self { 123 | Self { 124 | concurrency_limit: None, 125 | buffer: None, 126 | inner, 127 | _marker: PhantomData, 128 | } 129 | } 130 | } 131 | 132 | impl Server 133 | where 134 | Addr: Listenable, 135 | S: Service< 136 | (MsgContext, ApplicationResult), 137 | Response = Option<(MsgContext, ApplicationResult)>, 138 | Error = crate::Error, 139 | > + Send 140 | + 'static, 141 | S::Future: Send, 142 | Req: Send + Message + 'static, 143 | Resp: Send + Message + 'static, 144 | { 145 | pub async fn serve(self, addr: Addr) -> Result<(), ServerError> { 146 | let listen_stream = addr.bind().await?; 147 | let make_codec = DefaultMakeCodec::::new(); 148 | let mut incoming = Incoming::new(listen_stream, make_codec); 149 | 150 | let buffer = self.buffer.unwrap_or(DEFAULT_BUFFER); 151 | let concurrency_limit = self.concurrency_limit.unwrap_or(DEFAULT_CONCURRENCY_LIMIT); 152 | let service = ServiceBuilder::new() 153 | .buffer(buffer) 154 | .concurrency_limit(concurrency_limit) 155 | .service(self.inner); 156 | 157 | loop { 158 | match incoming.try_next().await? { 159 | Some(mut ts) => { 160 | let mut service = service.clone(); 161 | tokio::spawn(async move { 162 | loop { 163 | match ts.try_next().await { 164 | Ok(Some(req)) => { 165 | let ready_service = match service.ready().await { 166 | Ok(svc) => svc, 167 | Err(e) => { 168 | log::error!("service not ready error: {:?}", e); 169 | return; 170 | } 171 | }; 172 | 173 | let mut cx = req.0.clone(); 174 | match ready_service.call(req).await { 175 | Ok(Some((cx, resp))) => { 176 | if let Err(e) = ts.send((cx, resp)).await { 177 | log::error!("send reply back error: {}", e); 178 | return; 179 | } 180 | } 181 | Ok(None) => { 182 | // oneway does not need response 183 | } 184 | Err(e) => { 185 | // if oneway, we just return 186 | if cx.identifier.message_type == TMessageType::OneWay { 187 | return; 188 | } 189 | // if not oneway, we must send the exception back 190 | cx.identifier.message_type = TMessageType::Exception; 191 | let app_error = ApplicationError::new( 192 | ApplicationErrorKind::Unknown, 193 | e.to_string(), 194 | ); 195 | if let Err(e) = ts.send((cx, Err(app_error))).await { 196 | log::error!("send error back error: {}", e); 197 | return; 198 | } 199 | } 200 | } 201 | } 202 | Ok(None) => { 203 | return; 204 | } 205 | Err(e) => { 206 | // receive message error 207 | log::error!("error receiving message {}", e); 208 | return; 209 | } 210 | } 211 | } 212 | }); 213 | } 214 | None => return Ok(()), 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /chapter5/mini-lust/src/transport.rs: -------------------------------------------------------------------------------- 1 | use std::task::{Context, Poll}; 2 | 3 | use tokio_util::codec::Framed; 4 | use tower::make::MakeConnection; 5 | use tower::Service; 6 | 7 | use crate::codec::MakeCodec; 8 | use crate::utils::BoxFuture; 9 | 10 | pub struct FramedMakeTransport { 11 | make_connection: MCN, 12 | make_codec: MCC, 13 | } 14 | 15 | impl FramedMakeTransport { 16 | #[allow(unused)] 17 | pub fn new(make_connection: MCN, make_codec: MCC) -> Self { 18 | Self { 19 | make_connection, 20 | make_codec, 21 | } 22 | } 23 | } 24 | 25 | impl Service for FramedMakeTransport 26 | where 27 | MCN: MakeConnection, 28 | MCN::Future: 'static + Send, 29 | MCC: MakeCodec, 30 | MCC::Codec: 'static + Send, 31 | { 32 | type Response = Framed; 33 | type Error = MCN::Error; 34 | type Future = BoxFuture; 35 | 36 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 37 | self.make_connection.poll_ready(cx) 38 | } 39 | 40 | fn call(&mut self, target: TG) -> Self::Future { 41 | let conn_fut = self.make_connection.make_connection(target); 42 | let codec = self.make_codec.make_codec(); 43 | Box::pin(async move { 44 | let conn = conn_fut.await?; 45 | Ok(Framed::new(conn, codec)) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /chapter5/mini-lust/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::pin::Pin; 3 | 4 | pub type BoxFuture = Pin> + Send>>; 5 | -------------------------------------------------------------------------------- /chapter5/mini-lust/thrift/demo.thrift: -------------------------------------------------------------------------------- 1 | namespace rust demo 2 | 3 | struct User { 4 | 1: required i32 user_id, 5 | 2: required string user_name, 6 | 3: required bool is_male, 7 | 8 | 10: optional map extra, 9 | } 10 | 11 | struct GetUserRequest { 12 | 1: i32 user_id, 13 | 2: string user_name, 14 | 3: bool is_male, 15 | } 16 | 17 | struct GetUserResponse { 18 | 1: required list users, 19 | } 20 | 21 | service ItemService { 22 | GetUserResponse GetUser (1: GetUserRequest req, 2: bool shuffle), 23 | } -------------------------------------------------------------------------------- /chapter6/Chapter6-0.md: -------------------------------------------------------------------------------- 1 | # Thrift IDL Parsing 和 过程宏实现代码生成 2 | 3 | 本章内容较多,会分为几个小章节讲。 4 | 5 | - Chapter6-1: 基于 nom 的写 parser 极简教程 & 写 thrift parser 6 | - Chapter6-2: 基于 proc-macro2 和 darling 写过程宏 7 | - Chapter6-3: 代码生成器设计和编写 8 | -------------------------------------------------------------------------------- /chapter6/Chapter6-2.md: -------------------------------------------------------------------------------- 1 | # 过程宏 2 | 3 | 参考前一章节的手写版生成代码部分,我们根据 IDL 中定义的结构,一对一地生成了结构体和其对应的 Message 实现,以及辅助的匿名结构体和 Client、Server 代码。 4 | 5 | 占比相当多的代码是 Message 实现。这部分我们尝试利用过程宏(确切说是继承式过程宏)来实现。 6 | 7 | ## 0x00 派生过程宏 8 | 过程宏分为三种:(ref: #2) 9 | - 派生宏(Derive macro):用于结构体(struct)、枚举(enum)、联合(union)类型,可为其实现函数或特征(Trait)。 10 | - 属性宏(Attribute macro):用在结构体、字段、函数等地方,为其指定属性等功能。如标准库中的`#[inline]`、`#[derive(...)]`等都是属性宏。 11 | - 函数式宏(Function-like macro):用法与普通的规则宏类似,但功能更加强大,可实现任意语法树层面的转换功能。 12 | 13 | 要写一个派生宏,我们需要新创建一个 crate,并在 Cargo.toml 里指定 `proc-macro = true`。 14 | 15 | 这里我们创建 `mini-lust-macros` 并在其 `lib.rs` 中实现这个宏: 16 | ```rust 17 | #[proc_macro_derive(Message, attributes(mini_lust))] 18 | pub fn message(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 19 | // ... 20 | } 21 | ``` 22 | 23 | 我们可以通过 input 参数拿到 `TokenStream`,这个 TokenStream 即被 derive 的 struct、enum 或 union 的代码。 24 | 函数最终返回 TokenStream,即额外生成的代码。 25 | 26 | 我们往往要根据输入结构做代码生成,比如我们要实现类似 Debug 宏,就要知道这个结构是 struct 还是 enum 还是 union,以及有哪些 field,它们的类型都是什么。 27 | 28 | 那么显然我们需要 parse 这个结构定义。我们可以利用 `syn::parse_macro_input!` 得到这个解析后的结果。 29 | 30 | ## 0x01 attribute 提取 31 | 在我们的目标场景中,我们希望直接在生成的结构体上 derive 即可得到对应的 Message 实现。 32 | 33 | 由于我们的 Message 实现必须知道每个 field 的 id,而生成结构体里不可能包含这个信息。所以我们可以利用 attribute 来提供这个信息。 34 | 35 | ```rust 36 | #[derive(::mini_lust_macros::Message)] 37 | pub struct Friend { 38 | #[mini_lust(field_id = 1, required = "true", field_type = "i32")] 39 | id: i32, 40 | } 41 | ``` 42 | 43 | 目标生成代码: 44 | 45 | ```rust 46 | impl ::mini_lust_chap6::Message for Friend { 47 | fn encode( 48 | &self, 49 | cx: &::mini_lust_chap6::MsgContext, 50 | protocol: &mut T, 51 | ) -> ::mini_lust_chap6::Result<()> { 52 | protocol.write_struct_begin(&::mini_lust_chap6::TStructIdentifier { 53 | name: "Friend".to_string(), 54 | })?; 55 | let inner = &self.id; 56 | protocol.write_field_begin(&::mini_lust_chap6::TFieldIdentifier { 57 | name: Some("id".to_string()), 58 | field_type: ::mini_lust_chap6::TType::I32, 59 | id: Some(1i16), 60 | })?; 61 | protocol.write_i32(*inner)?; 62 | protocol.write_field_end()?; 63 | protocol.write_field_stop()?; 64 | protocol.write_struct_end()?; 65 | Ok(()) 66 | } 67 | fn decode( 68 | cx: &mut ::mini_lust_chap6::MsgContext, 69 | protocol: &mut T, 70 | ) -> ::mini_lust_chap6::Result { 71 | let mut field_id = None; 72 | protocol.read_struct_begin()?; 73 | loop { 74 | let ident = protocol.read_field_begin()?; 75 | if ident.field_type == ::mini_lust_chap6::TType::Stop { 76 | break; 77 | } 78 | match ident.id { 79 | Some(1i16) => { 80 | ::mini_lust_chap6::ttype_comparing( 81 | ident.field_type, 82 | ::mini_lust_chap6::TType::I32, 83 | )?; 84 | let content = protocol.read_i32()?; 85 | field_id = Some(content); 86 | } 87 | _ => { 88 | protocol.skip(ident.field_type)?; 89 | } 90 | } 91 | protocol.read_field_end()?; 92 | } 93 | protocol.read_struct_end()?; 94 | let output = Self { 95 | id: field_id.ok_or_else(|| { 96 | ::mini_lust_chap6::new_protocol_error( 97 | ::mini_lust_chap6::ProtocolErrorKind::InvalidData, 98 | "field id is required", 99 | ) 100 | })?, 101 | }; 102 | Ok(output) 103 | } 104 | } 105 | ``` 106 | 107 | 提取字段的 attribute 并不复杂,我们可以直接从 syn 解析到的结构中拿到 fields,并从 field 的 attrs 中读到所有的 attribute,并过滤出带有我们 mini_lust 标记的属性,再自行解析出来。 108 | 109 | 而这个相对通用的过程有一个叫 [darling](https://crates.io/crates/darling) 的库可以快速帮我们完成。 110 | 111 | 我们定义 Receiver 结构体: 112 | ```rust 113 | #[derive(Debug, FromDeriveInput)] 114 | #[darling(attributes(mini_lust))] 115 | pub(crate) struct Receiver { 116 | pub ident: syn::Ident, 117 | pub generics: syn::Generics, 118 | pub data: ast::Data, 119 | } 120 | ``` 121 | 其中,EnumReceiver 对应 enum 情况下的接收器,FieldReceiver 对应 struct 情况下的接收器。 122 | 123 | 例如,我们可以定义 FieldReceiver: 124 | ```rust 125 | #[derive(Debug, FromField)] 126 | #[darling(attributes(mini_lust))] 127 | pub(crate) struct FieldReceiver { 128 | pub ident: Option, 129 | pub ty: syn::Type, 130 | 131 | pub field_type: String, 132 | pub field_id: i32, 133 | #[darling(default)] 134 | pub required: Required, 135 | } 136 | ``` 137 | 138 | 之后我们就可以将 parse 后的结构丢进去继续解析: 139 | ```rust 140 | let parsed = syn::parse_macro_input!(input as syn::DeriveInput); 141 | let receiver = Receiver::from_derive_input(&parsed); 142 | ``` 143 | 144 | 这时我们便能直接拿到解析后的 field_type 和 field_id 等标记。 145 | 146 | ## 0x02 生成代码 147 | 与 rust 编译器打交道的时候,我们接收和返回的是 `proc_macro::TokenStream`。 148 | 149 | 但是这个接口和实现过于简单,不易使用,我们一般会将其转换为 `proc_macro2::TokenStream` 使用。 150 | 151 | 当生成代码时,我们可以通过 quote 库来快速包装代码: 152 | ```rust 153 | quote::quote! { 154 | impl XXX for YYY { 155 | // ... 156 | } 157 | } 158 | ``` 159 | 这时便会生成 `impl` 代码对应的 TokenStream。 160 | 161 | 最后通过 `.into()` 可以快速将 `proc_macro2::TokenStream` 转换为需要的 `proc_macro::TokenStream`。 162 | 163 | 我们通过: 164 | ```rust 165 | let ts2 = quote::quote! { 166 | impl ::mini_lust_chap6::Message for #name #generics { 167 | fn encode(&self, cx: &::mini_lust_chap6::MsgContext, protocol: &mut T) -> ::mini_lust_chap6::Result<()> { 168 | #tok_enc 169 | } 170 | 171 | fn decode(cx: &mut ::mini_lust_chap6::MsgContext, protocol: &mut T) -> ::mini_lust_chap6::Result { 172 | #tok_dec 173 | } 174 | } 175 | }; 176 | proc_macro::TokenStream::from(ts2) 177 | ``` 178 | 生成并返回代码。详细的生成细节可以参考本章附的代码 mini-lust-macros。 179 | 180 | ## 0x03 测试 181 | 我们写好了 derive 宏之后要如何测试呢?可以利用 `cargo-expand` 来输出宏展开后的代码。 182 | 183 | ```bash 184 | cargo install cargo-expand 185 | ``` 186 | 187 | 之后我们可以使用 `cargo expand` 来展开代码。你可以尝试在本章附的 demo-derive-macro 包下执行这个命令。 188 | 189 | 至此,我们利用过程宏实现了对带 attribute 的结构体的 Message 实现生成;并通过 cargo expand 验证展开后的代码。 190 | 191 | ## 参考链接 192 | 1. [如何编写一个过程宏(proc-macro)](https://dengjianping.github.io/2019/02/28/%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E8%BF%87%E7%A8%8B%E5%AE%8F(proc-macro).html) 193 | 2. [Rust过程宏入门](https://zhuanlan.zhihu.com/p/342408254) -------------------------------------------------------------------------------- /chapter6/Chapter6-3.md: -------------------------------------------------------------------------------- 1 | # 代码生成 2 | 在前面两个小节,我们实现了 thrift 的 parser 和 Message derive 宏。 3 | 4 | 我们需要写一个生成器把它们串起来,并生成结构体定义和其余代码(有 derive 宏可以省去在本步生成 Message 实现的麻烦)。 5 | 6 | 在我们的设计中,用户会在代码里通过 `build.rs` 来触发代码生成,然后在需要使用的地方使用 `include!` 宏来引入生成代码。 7 | 8 | ## 0x00 最简 demo 9 | 如果我们只考虑最简单的一个情况:用户需要生成一个 thrift 文件,且该文件并未 include 别的文件。 10 | 11 | 这时我们的 Builder 需要做的事情是:1. 读取这个文件 2. Parse 这个文件 3. 使用 Parse 后的结果生成代码 4. 将代码写入目标文件 12 | 13 | 我们先跳过第 3 步,将整个 Builder 完成(参考 demo-generator)。 14 | 15 | ```rust 16 | pub struct SimpleBuilder { 17 | file: Option, 18 | } 19 | 20 | impl SimpleBuilder { 21 | pub fn new() -> Self { 22 | Self {file: None} 23 | } 24 | 25 | pub fn with_file>(mut self, p: P) -> Self { 26 | self.file = Some(p.into()); 27 | self 28 | } 29 | 30 | pub fn build(self) { 31 | let idl = std::fs::read_to_string(self.file.expect("idl path must be specified")).unwrap(); 32 | let (_, document) = thrift_parser::document::Document::parse(&idl).unwrap(); 33 | 34 | // TODO: document -> code 35 | let code = quote! { 36 | pub fn demo() -> String { 37 | "DEMO".to_string() 38 | } 39 | }; 40 | 41 | // We will get OUT_DIR when build. However, in test the env not exists, so we use 42 | // ${CARGO_MANIFEST_DIR}/target. It's not a precise path. 43 | let output_dir = std::env::var("OUT_DIR") 44 | .unwrap_or_else(|_| std::env::var("CARGO_MANIFEST_DIR").unwrap() + "/target"); 45 | std::fs::create_dir(&output_dir); 46 | let mut output_path = std::path::PathBuf::from(output_dir); 47 | output_path.push("gen.rs"); 48 | let mut output_file = std::fs::File::create(output_path).unwrap(); 49 | output_file.write(code.to_string().as_ref()).unwrap(); 50 | } 51 | } 52 | 53 | #[test] 54 | fn simple_build() { 55 | let mut idl_path = 56 | std::path::PathBuf::from_str(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).unwrap(); 57 | idl_path.extend(vec!["thrift", "demo.thrift"]); 58 | SimpleBuilder::new().with_file(idl_path).build(); 59 | } 60 | ``` 61 | 62 | 运行后,我们会在 `gen.rs` 中找到生成代码: `pub fn demo () -> String { "DEMO" . to_string () }` 。 63 | 64 | ## 0x01 通过 Build 脚本自动由 IDL 生成代码 65 | 前一小节的代码十分简单,但事实上代码生成并没有这么简单!我们有一些问题需要想清楚。 66 | 67 | ### 0x01.0 层级的问题 68 | 别忘了,thrift 中支持通过 include 引入其他 thrift 文件。 69 | 70 | 有两个需要考虑的问题: 71 | 1. IDL 目录层级区分:IDL 文件中的依赖路径 和 生成代码中的依赖路径 72 | 2. 多个 Thrift 及其共同依赖:我们不能重复为一个 thrift 文件生成代码 73 | 74 | thrift 中通过 namespace 指定了生成后的 mod 结构,而 thrift 文件中的 include 仅仅表示相对于本文件的被依赖文件的路径。 75 | 76 | ### 0x01.1 节点表示 77 | 因为 thrift 文件是相互依赖的一个树形结构,很常规的一个思路就是将每个文件抽象为树中的一个节点。它包含了从文件内容 parse 出的结果 Document。 78 | ```rust 79 | pub struct Node { 80 | // document content, we read and parse it from file 81 | document: Document, 82 | } 83 | ``` 84 | 85 | #### namespace 问题 86 | 前面提到了,我们可以在 thrift 文件中指定 namespace,通过 namespace 我们得知了生成后的代码的层次结构。 87 | 88 | 如 namespace 为 `a::b::c`,那么我们会将代码生成到: 89 | ```rust 90 | pub mod a { pub mod b { pub mod c { 91 | // here 92 | }}} 93 | ``` 94 | 95 | 通常情况下我们可以从 Document 里得到这个 namespace。那如果 IDL 里没有指定 namespace 呢?我们就只能用文件名作为 namespace 了。 96 | 97 | 这样也说明仅仅依靠 parse 出来的结果是不够的。所以我们额外在 Node 定义中加入这个字段: 98 | ```rust 99 | pub struct Node { 100 | // document content, we read and parse it from file 101 | document: Document, 102 | // namespace is defined in IDL file like "a.b.c", if no namespace found, the 103 | // filename in snake case is used. 104 | namespace: String, 105 | } 106 | ``` 107 | 108 | #### 相对路径问题 109 | thrift 文件中的 include 路径并非相对与当前工作目录,而是相对于当前 thrift 文件。 110 | 111 | 而这个路径显然仅仅从 Document 结构是拿不到的,因为 thrift 文件内容不可能包含它当前所在的路径信息。 112 | 113 | 所以我们需要继续扩展 Node 定义: 114 | ```rust 115 | pub struct Node { 116 | // document content, we read and parse it from file 117 | document: Document, 118 | // file_abs is IDL file abs path 119 | file_abs: PathBuf, 120 | // namespace is defined in IDL file like "a.b.c", if no namespace found, the 121 | // filename in snake case is used. 122 | namespace: String, 123 | } 124 | ``` 125 | 126 | 此时,Node 已经包含了足够的信息,可以去生成它以及它所依赖的文件。 127 | 128 | ### 0x01.2 成环检测 129 | 如果用户给了一个有环的 thrift 结构,比起生成器卡死,我们更希望能够报错。 130 | 131 | 我们可以在遍历生成这棵树的时候,带一个可写的 Vec 来记录当前所在的路径。 132 | 每当走到一个新的节点,我们会先检查当前节点有没有出现在走过的路上,如果有,说明成环,报错退出。 133 | 134 | 为了维护这个路径,我们可以在进入新节点时 push 新节点名字;在离开时 pop 掉。 135 | 136 | ### 0x01.3 避免重复生成 137 | 如果多个文件都依赖了同一个文件,就会导致重复遍历。这时我们不希望重复生成,因为这样一定会导致命名冲突。 138 | 139 | 我们可以在遍历生成这棵树的时候,带一个可写的 HashSet 来记录所有走过的节点。 140 | 每当走到一个新节点,除了上一步的成环检查,我们还需要在这之后检查是否已生成过。如果已经生成过该节点,则直接跳过即可。 141 | 142 | 在离开节点时,将上一步 pop 掉的该节点名加入 HashSet。 143 | 144 | ### 0x01.4 为节点生成代码 145 | 由于前面提到的两个问题,我们需要在生成时带上可写的 generated 和 generating,并带一个可写的 output 方便递归写。 146 | ```rust 147 | impl Node { 148 | /// Generate token recursively to output. 149 | pub fn generate( 150 | &self, 151 | generated: &mut HashSet, 152 | generating: &mut Vec, 153 | output: &mut TokenStream, 154 | ) -> GenerateResult<()> { 155 | // generating 检查(判断成环) 156 | // generated 检查(判断重复生成) 157 | // 标记为 generating 158 | // 读依赖并构造所有依赖 Node 159 | // 所有依赖 Node.generate(generated, generating, output) 160 | // 本文件生成 161 | // 取消 generating 标记并标记为 generated 162 | } 163 | } 164 | ``` 165 | 166 | ## 0x02 自动生成结构体 167 | 168 | ### 0x02.0 信息传递 169 | 我们在 Node 定义里补充了 2 个 Document 里可能没有的信息:file_abs 和 namespace。 170 | 171 | file_abs 用于在 generate 实现中读取依赖文件,namespace 是要提供给 Document 用于生成代码的。 172 | 除此之外,需要提供给 Document 的信息还有它所有 include 对应的 namespace。 173 | 174 | 所以我们定义了一个结构体: 175 | ```rust 176 | pub struct CodeGenContext { 177 | // namespaces of all files that it contains, eg: a::b::c, c::d::e 178 | includes: Vec, 179 | // namespaces, eg: a::b::C -> [a, b, c] 180 | namespaces: Vec, 181 | } 182 | ``` 183 | 184 | 并定义了一个 trait 用于生成代码: 185 | ```rust 186 | pub trait CodeGenWithContext { 187 | fn gen_token(&self, cx: &CodeGenContext) -> CodeGenResult { 188 | let mut stream = TokenStream::new(); 189 | let _ = self.write_token(cx, &mut stream)?; 190 | Ok(stream) 191 | } 192 | fn write_token(&self, cx: &CodeGenContext, output: &mut TokenStream) -> CodeGenResult<()>; 193 | } 194 | ``` 195 | 196 | 之后我们为 Document 实现这个 trait 即可。 197 | 198 | ### 0x02.1 CodeGenWithContext 实现 199 | 我们将 Document 的所有 include 生成为 `pub use XXX`; 200 | 并为所有的 struct 和 service 调用各自的生成函数; 201 | 最后将已经生成的代码包装进它所在的 namespace:`pub mod A {pub mod B {pub moc C {}}}`。 202 | 203 | ```rust 204 | impl CodeGenWithContext for Document { 205 | fn write_token(&self, cx: &CodeGenContext, output: &mut TokenStream) -> CodeGenResult<()> { 206 | let mut generated = TokenStream::new(); 207 | 208 | // generate include 209 | // We may not use includes of self since they are the file system path instead of 210 | // their namespace. 211 | // So the includes is set with CodeGenContext. 212 | for inc in cx.includes.iter() { 213 | let parts = inc 214 | .split("::") 215 | .map(|p| quote::format_ident!("{}", p)) 216 | .collect::>(); 217 | generated.extend(quote::quote! { 218 | pub use #(#parts)::*; 219 | }) 220 | } 221 | 222 | // generate struct 223 | for stut in self.structs.iter() { 224 | let _ = stut.write_token(&mut generated)?; 225 | } 226 | 227 | // generate service 228 | for service in self.services.iter() { 229 | let _ = service.write_token(&mut generated)?; 230 | } 231 | 232 | // generate namespaces, it will wrap the generated above. 233 | // We may not use namespaces of self since we only want to use scope rs or *. 234 | // Also, if no namespace exists, we want to use the file stem and self does not 235 | // know it. 236 | // So the namespace is set with CodeGenContext. 237 | for m in cx.namespaces.iter().rev() { 238 | let ident = quote::format_ident!("{}", m); 239 | generated = quote::quote! { 240 | pub mod #ident { 241 | #generated 242 | } 243 | } 244 | } 245 | // write to output 246 | output.extend(generated); 247 | Ok(()) 248 | } 249 | } 250 | ``` 251 | 252 | ### 0x02.2 struct 和 service 生成 253 | struct 生成主要是生成所有字段和 annotation。 254 | 255 | service 需要生成一些相关的匿名结构体和客户端、服务端结构以及其实现。 256 | 257 | 本部分代码较多且较为机械,不贴了。 258 | -------------------------------------------------------------------------------- /chapter6/demo-derive-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo-derive-macro" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | mini-lust-macros = { path = "../mini-lust-macros" } 9 | 10 | # Dependency of generated code 11 | mini-lust-chap6 = { path = "../mini-lust" } -------------------------------------------------------------------------------- /chapter6/demo-derive-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use mini_lust_chap6::{OrigType, TType}; 4 | 5 | #[derive(::mini_lust_macros::Message)] 6 | pub struct Friend { 7 | #[mini_lust(field_id = 1, required = "true", field_type = "i32")] 8 | id: i32, 9 | } 10 | 11 | impl OrigType for Friend {} 12 | 13 | #[derive(::mini_lust_macros::Message)] 14 | pub struct TestUser { 15 | #[mini_lust(field_id = 1, required = "true", field_type = "i32")] 16 | id: i32, 17 | #[mini_lust(field_id = 2, required = "true", field_type = "string")] 18 | name: String, 19 | #[mini_lust(field_id = 3, field_type = "map(string, list(string))")] 20 | accounts: Option>>, 21 | #[mini_lust(field_id = 4, required = "true", field_type = "list(ident(Friend))")] 22 | friends: Vec, 23 | } 24 | 25 | impl OrigType for TestUser {} 26 | 27 | #[derive(::mini_lust_macros::Message)] 28 | #[mini_lust(dispatch_only = true)] 29 | pub enum MyArgs { 30 | MakeFriend(Friend), 31 | CreateTestUser(TestUser), 32 | } 33 | 34 | #[derive(::mini_lust_macros::Message)] 35 | pub enum MyResult { 36 | #[mini_lust(field_id = 1)] 37 | Success(Friend), 38 | #[mini_lust(field_id = 2)] 39 | Exception(TestUser), 40 | } 41 | 42 | -------------------------------------------------------------------------------- /chapter6/demo-example-app-generated/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-app-generated" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | # framework self 10 | mini-lust-macros = { path = "../mini-lust-macros" } 11 | mini-lust-chap6 = { path = "../mini-lust" } 12 | 13 | # framework generated dependency 14 | async-trait = "0.1" 15 | tokio = { version = "1.9", feature = ["full"] } 16 | tower = { version = "0.4", features = ["make", "balance", "discover", "util", "limit", "buffer"] } 17 | 18 | # example dependency 19 | log = "0.4" 20 | env_logger = "0.9" 21 | 22 | [[bin]] 23 | name = "client" 24 | path = "src/client.rs" 25 | 26 | [[bin]] 27 | name = "server" 28 | path = "src/server.rs" -------------------------------------------------------------------------------- /chapter6/demo-example-app-generated/src/client.rs: -------------------------------------------------------------------------------- 1 | use mini_lust_chap6::SocketOrUnix; 2 | use crate::generated::demo::{GetUserRequest, ItemServiceClientBuilder}; 3 | 4 | mod generated; 5 | 6 | #[tokio::main] 7 | async fn main() { 8 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 9 | 10 | let target = SocketOrUnix::Socket("127.0.0.1:12345".parse().unwrap()); 11 | let mut client = ItemServiceClientBuilder::new(target).build(); 12 | 13 | let resp = client 14 | .GetUser( 15 | GetUserRequest { 16 | user_id: Some(1), 17 | user_name: Some("ihciah".to_string()), 18 | is_male: Some(false), 19 | }, 20 | true, 21 | ) 22 | .await 23 | .unwrap(); 24 | log::info!("{:?}", resp); 25 | } 26 | -------------------------------------------------------------------------------- /chapter6/demo-example-app-generated/src/generated.rs: -------------------------------------------------------------------------------- 1 | pub mod demo { 2 | #[derive(mini_lust_macros :: Message, Debug, Clone, PartialEq)] 3 | pub struct User { 4 | #[mini_lust(field_id = 1i16, required = "true", field_type = "i32")] 5 | pub user_id: i32, 6 | #[mini_lust(field_id = 2i16, required = "true", field_type = "string")] 7 | pub user_name: ::std::string::String, 8 | #[mini_lust(field_id = 3i16, required = "true", field_type = "bool")] 9 | pub is_male: bool, 10 | #[mini_lust( 11 | field_id = 10i16, 12 | required = "false", 13 | field_type = "map(string, string)" 14 | )] 15 | pub extra: ::std::option::Option< 16 | ::std::collections::BTreeMap<::std::string::String, ::std::string::String>, 17 | >, 18 | } 19 | impl ::mini_lust_chap6::OrigType for User {} 20 | #[derive(mini_lust_macros :: Message, Debug, Clone, PartialEq)] 21 | pub struct GetUserRequest { 22 | #[mini_lust(field_id = 1i16, field_type = "i32")] 23 | pub user_id: ::std::option::Option, 24 | #[mini_lust(field_id = 2i16, field_type = "string")] 25 | pub user_name: ::std::option::Option<::std::string::String>, 26 | #[mini_lust(field_id = 3i16, field_type = "bool")] 27 | pub is_male: ::std::option::Option, 28 | } 29 | impl ::mini_lust_chap6::OrigType for GetUserRequest {} 30 | #[derive(mini_lust_macros :: Message, Debug, Clone, PartialEq)] 31 | pub struct GetUserResponse { 32 | #[mini_lust(field_id = 1i16, required = "true", field_type = "list(ident(User))")] 33 | pub users: ::std::vec::Vec, 34 | } 35 | impl ::mini_lust_chap6::OrigType for GetUserResponse {} 36 | #[derive(mini_lust_macros :: Message, Debug, Clone, PartialEq)] 37 | pub struct AnonymousItemServiceGetUserArgs { 38 | #[mini_lust(field_id = 1i16, field_type = "ident(GetUserRequest)")] 39 | pub req: ::std::option::Option, 40 | #[mini_lust(field_id = 2i16, field_type = "bool")] 41 | pub shuffle: ::std::option::Option, 42 | } 43 | impl ::mini_lust_chap6::OrigType for AnonymousItemServiceGetUserArgs {} 44 | #[derive(mini_lust_macros :: Message, Debug, Clone, PartialEq)] 45 | pub enum AnonymousItemServiceGetUserResult { 46 | #[mini_lust(field_id = 1)] 47 | Success(GetUserResponse), 48 | } 49 | impl ::mini_lust_chap6::OrigType for AnonymousItemServiceGetUserResult {} 50 | #[derive(mini_lust_macros :: Message, Debug, Clone, PartialEq)] 51 | #[mini_lust(dispatch_only = true)] 52 | pub enum AnonymousItemServiceRequest { 53 | GetUser(AnonymousItemServiceGetUserArgs), 54 | } 55 | impl ::mini_lust_chap6::OrigType for AnonymousItemServiceRequest {} 56 | #[derive(mini_lust_macros :: Message, Debug, Clone, PartialEq)] 57 | #[mini_lust(dispatch_only = true)] 58 | pub enum AnonymousItemServiceResponse { 59 | GetUser(AnonymousItemServiceGetUserResult), 60 | } 61 | impl ::mini_lust_chap6::OrigType for AnonymousItemServiceResponse {} 62 | pub struct ItemServiceClientBuilder { 63 | client_builder: ::mini_lust_chap6::ClientBuilder< 64 | ::mini_lust_chap6::DefaultMakeCodec< 65 | AnonymousItemServiceRequest, 66 | AnonymousItemServiceResponse, 67 | >, 68 | >, 69 | } 70 | impl ItemServiceClientBuilder { 71 | pub fn new(target: ::mini_lust_chap6::SocketOrUnix) -> Self { 72 | let client_builder = ::mini_lust_chap6::ClientBuilder::new(target); 73 | Self { client_builder } 74 | } 75 | pub fn build(self) -> ItemServiceClient { 76 | ItemServiceClient::new(self.client_builder.build()) 77 | } 78 | } 79 | #[derive(Clone)] 80 | pub struct ItemServiceClient { 81 | inner_client: 82 | ::mini_lust_chap6::Client, 83 | } 84 | impl ItemServiceClient { 85 | pub fn new( 86 | inner: ::mini_lust_chap6::Client< 87 | AnonymousItemServiceRequest, 88 | AnonymousItemServiceResponse, 89 | >, 90 | ) -> Self { 91 | Self { 92 | inner_client: inner, 93 | } 94 | } 95 | } 96 | impl ItemServiceClient { 97 | pub async fn GetUser( 98 | &mut self, 99 | req: GetUserRequest, 100 | shuffle: bool, 101 | ) -> ::mini_lust_chap6::Result { 102 | let anonymous_request = 103 | AnonymousItemServiceRequest::GetUser(AnonymousItemServiceGetUserArgs { 104 | req: Some(req), 105 | shuffle: Some(shuffle), 106 | }); 107 | let resp = self.inner_client.call("GetUser", anonymous_request).await?; 108 | #[allow(irrefutable_let_patterns)] 109 | if let AnonymousItemServiceResponse::GetUser(r) = resp { 110 | return Ok(r); 111 | } 112 | Err(::mini_lust_chap6::new_application_error( 113 | ::mini_lust_chap6::ApplicationErrorKind::Unknown, 114 | "unable to get response", 115 | )) 116 | } 117 | } 118 | #[async_trait::async_trait] 119 | pub trait ItemService { 120 | async fn GetUser( 121 | &self, 122 | req: ::std::option::Option, 123 | shuffle: ::std::option::Option, 124 | ) -> ::mini_lust_chap6::ApplicationResult; 125 | } 126 | pub struct ItemServiceServer { 127 | inner: ::std::sync::Arc, 128 | } 129 | impl ItemServiceServer { 130 | pub fn new(inner: S) -> Self { 131 | Self { 132 | inner: ::std::sync::Arc::new(inner), 133 | } 134 | } 135 | } 136 | impl 137 | ::tower::Service<( 138 | ::mini_lust_chap6::MsgContext, 139 | ::mini_lust_chap6::ApplicationResult, 140 | )> for ItemServiceServer 141 | where 142 | S: ItemService + Send + Sync + 'static, 143 | { 144 | type Response = Option<( 145 | ::mini_lust_chap6::MsgContext, 146 | ::mini_lust_chap6::ApplicationResult, 147 | )>; 148 | type Error = ::mini_lust_chap6::Error; 149 | type Future = ::mini_lust_chap6::BoxFuture; 150 | fn poll_ready( 151 | &mut self, 152 | _cx: &mut ::std::task::Context, 153 | ) -> ::std::task::Poll> { 154 | ::std::task::Poll::Ready(Ok(())) 155 | } 156 | fn call( 157 | &mut self, 158 | req: ( 159 | ::mini_lust_chap6::MsgContext, 160 | ::mini_lust_chap6::ApplicationResult, 161 | ), 162 | ) -> Self::Future { 163 | let inner = self.inner.clone(); 164 | ::std::boxed::Box::pin(async move { 165 | let (mut cx, req) = req; 166 | match req { 167 | Ok(AnonymousItemServiceRequest::GetUser(r)) => { 168 | let ret = inner.GetUser(r.req, r.shuffle).await; 169 | match ret { 170 | Ok(r) => { 171 | cx.identifier.message_type = ::mini_lust_chap6::TMessageType::Reply; 172 | Ok(Some((cx, Ok(AnonymousItemServiceResponse::GetUser(r))))) 173 | } 174 | Err(e) => { 175 | cx.identifier.message_type = 176 | ::mini_lust_chap6::TMessageType::Exception; 177 | Ok(Some((cx, Err(e)))) 178 | } 179 | } 180 | } 181 | Err(e) => { 182 | log::error!("unexpected client error: {}", e); 183 | Err(::mini_lust_chap6::new_application_error( 184 | ::mini_lust_chap6::ApplicationErrorKind::Unknown, 185 | "unexpected client error", 186 | )) 187 | } 188 | } 189 | }) 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /chapter6/demo-example-app-generated/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use mini_lust_chap6::{ApplicationResult, Server}; 4 | 5 | use crate::generated::demo::{ 6 | AnonymousItemServiceGetUserResult, GetUserRequest, GetUserResponse, ItemService, 7 | ItemServiceServer, User, 8 | }; 9 | 10 | mod generated; 11 | 12 | struct Svc; 13 | 14 | #[async_trait::async_trait] 15 | impl ItemService for Svc { 16 | async fn GetUser( 17 | &self, 18 | req: Option, 19 | shuffle: Option, 20 | ) -> ApplicationResult { 21 | log::info!( 22 | "receive a get_user request: req = {:?}, shuffle = {:?}", 23 | req, 24 | shuffle 25 | ); 26 | 27 | let req = req.unwrap(); 28 | let resp = GetUserResponse { 29 | users: vec![User { 30 | user_id: req.user_id.unwrap(), 31 | user_name: req.user_name.unwrap(), 32 | is_male: shuffle.unwrap(), 33 | extra: None, 34 | }] 35 | }; 36 | Ok(AnonymousItemServiceGetUserResult::Success(resp)) 37 | } 38 | } 39 | 40 | #[tokio::main] 41 | async fn main() { 42 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 43 | 44 | let server = Server::new(ItemServiceServer::new(Svc)); 45 | let addr = "127.0.0.1:12345".parse::().unwrap(); 46 | 47 | log::info!("Will serve on 127.0.0.1:12345"); 48 | let _ = server.serve(addr).await; 49 | } 50 | -------------------------------------------------------------------------------- /chapter6/demo-example-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-app" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | # framework self 10 | mini-lust-macros = { path = "../mini-lust-macros" } 11 | mini-lust-chap6 = { path = "../mini-lust" } 12 | 13 | # framework generated dependency 14 | async-trait = "0.1" 15 | tokio = { version = "1.9", feature = ["full"] } 16 | tower = { version = "0.4", features = ["make", "balance", "discover", "util", "limit", "buffer"] } 17 | 18 | # example dependency 19 | log = "0.4" 20 | env_logger = "0.9" 21 | 22 | [[bin]] 23 | name = "client" 24 | path = "src/client.rs" 25 | 26 | [[bin]] 27 | name = "server" 28 | path = "src/server.rs" -------------------------------------------------------------------------------- /chapter6/demo-example-app/src/client.rs: -------------------------------------------------------------------------------- 1 | use mini_lust_chap6::SocketOrUnix; 2 | 3 | use crate::generated::{GetUserRequest, ItemServiceClientBuilder}; 4 | 5 | mod generated; 6 | 7 | #[tokio::main] 8 | async fn main() { 9 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 10 | 11 | let target = SocketOrUnix::Socket("127.0.0.1:12345".parse().unwrap()); 12 | let mut client = ItemServiceClientBuilder::new(target).build(); 13 | 14 | let resp = client 15 | .get_user( 16 | GetUserRequest { 17 | user_id: Some(1), 18 | user_name: Some("ihciah".to_string()), 19 | is_male: Some(false), 20 | }, 21 | true, 22 | ) 23 | .await 24 | .unwrap(); 25 | log::info!("{:?}", resp); 26 | } 27 | -------------------------------------------------------------------------------- /chapter6/demo-example-app/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use mini_lust_chap6::{ApplicationResult, Server}; 4 | 5 | use crate::generated::{ 6 | AnonymousItemServiceGetUserResult, GetUserRequest, GetUserResponse, ItemService, 7 | ItemServiceServer, User, 8 | }; 9 | 10 | mod generated; 11 | 12 | struct Svc; 13 | 14 | #[async_trait::async_trait] 15 | impl ItemService for Svc { 16 | async fn get_user( 17 | &self, 18 | req: Option, 19 | shuffle: Option, 20 | ) -> ApplicationResult { 21 | log::info!( 22 | "receive a get_user request: req = {:?}, shuffle = {:?}", 23 | req, 24 | shuffle 25 | ); 26 | 27 | let mut resp = GetUserResponse::default(); 28 | let req = req.unwrap(); 29 | resp.users.push(User { 30 | user_id: req.user_id.unwrap(), 31 | user_name: req.user_name.unwrap(), 32 | is_male: shuffle.unwrap(), 33 | extra: None, 34 | }); 35 | Ok(AnonymousItemServiceGetUserResult::Success(resp)) 36 | } 37 | } 38 | 39 | #[tokio::main] 40 | async fn main() { 41 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 42 | 43 | let server = Server::new(ItemServiceServer::new(Svc)); 44 | let addr = "127.0.0.1:12345".parse::().unwrap(); 45 | 46 | log::info!("Will serve on 127.0.0.1:12345"); 47 | let _ = server.serve(addr).await; 48 | } 49 | -------------------------------------------------------------------------------- /chapter6/demo-generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo-generator" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | thrift-parser = { path = "../thrift-parser" } 9 | quote = "1" 10 | -------------------------------------------------------------------------------- /chapter6/demo-generator/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use quote::quote; 4 | 5 | use thrift_parser::Parser; 6 | 7 | pub struct SimpleBuilder { 8 | file: Option, 9 | } 10 | 11 | impl SimpleBuilder { 12 | pub fn new() -> Self { 13 | Self { file: None } 14 | } 15 | 16 | pub fn with_file>(mut self, p: P) -> Self { 17 | self.file = Some(p.into()); 18 | self 19 | } 20 | 21 | pub fn build(self) { 22 | let idl = std::fs::read_to_string(self.file.expect("idl path must be specified")).unwrap(); 23 | let (_, document) = thrift_parser::document::Document::parse(&idl).unwrap(); 24 | 25 | // TODO: document -> code 26 | let code = quote! { 27 | pub fn demo() -> String { 28 | "DEMO".to_string() 29 | } 30 | }; 31 | 32 | // We will get OUT_DIR when build. However, in test the env not exists, so we use 33 | // ${CARGO_MANIFEST_DIR}/target. It's not a precise path. 34 | let output_dir = std::env::var("OUT_DIR") 35 | .unwrap_or_else(|_| std::env::var("CARGO_MANIFEST_DIR").unwrap() + "/target"); 36 | std::fs::create_dir(&output_dir); 37 | let mut output_path = std::path::PathBuf::from(output_dir); 38 | output_path.push("gen.rs"); 39 | let mut output_file = std::fs::File::create(output_path).unwrap(); 40 | let _ = output_file.write(code.to_string().as_ref()).unwrap(); 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use std::str::FromStr; 47 | 48 | use crate::SimpleBuilder; 49 | 50 | #[test] 51 | fn simple_build() { 52 | let mut idl_path = 53 | std::path::PathBuf::from_str(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).unwrap(); 54 | idl_path.extend(vec!["thrift", "demo.thrift"]); 55 | SimpleBuilder::new().with_file(idl_path).build(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /chapter6/demo-generator/thrift/demo.thrift: -------------------------------------------------------------------------------- 1 | namespace rust demo 2 | 3 | struct A { 4 | 1: required i32 user_id, 5 | 2: required string user_name, 6 | 3: required bool is_male, 7 | 10: optional map extra, 8 | } 9 | 10 | struct User { 11 | 1: required i32 user_id, 12 | 2: required string user_name, 13 | 3: required bool is_male, 14 | 15 | 10: optional map extra, 16 | } 17 | 18 | struct GetUserRequest { 19 | 1: i32 user_id, 20 | 2: string user_name, 21 | 3: bool is_male, 22 | } 23 | 24 | struct GetUserResponse { 25 | 1: required list users, 26 | } 27 | 28 | service ItemService { 29 | GetUserResponse GetUser (1: GetUserRequest req, 2: bool shuffle), 30 | } -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mini-lust-generator" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | thrift-parser = { path = "../thrift-parser" } 9 | proc-macro2 = "1" 10 | quote = "1" 11 | thiserror = "1" 12 | heck = "0.3" 13 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/code_gen/basic.rs: -------------------------------------------------------------------------------- 1 | use heck::{CamelCase, SnakeCase}; 2 | use proc_macro2::TokenStream; 3 | use quote::ToTokens; 4 | 5 | use thrift_parser::basic::Identifier; 6 | 7 | use crate::code_gen::errors::CodeGenResult; 8 | use crate::code_gen::IdentifierGen; 9 | 10 | impl IdentifierGen for Identifier { 11 | fn field_name(&self) -> CodeGenResult { 12 | let name = quote::format_ident!("{}", self.as_str().to_snake_case().to_lowercase()); 13 | Ok(name.to_token_stream()) 14 | } 15 | 16 | fn ident_name(&self) -> CodeGenResult { 17 | let name = quote::format_ident!("{}", self.as_str().to_camel_case()); 18 | Ok(name.to_token_stream()) 19 | } 20 | 21 | fn struct_name(&self) -> CodeGenResult { 22 | let name = quote::format_ident!("{}", self.as_str().replace(".", "::")); 23 | Ok(name.to_token_stream()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/code_gen/document.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | use thrift_parser::document::Document; 4 | 5 | use crate::code_gen::errors::CodeGenResult; 6 | use crate::code_gen::{CodeGen, CodeGenContext, CodeGenWithContext}; 7 | 8 | impl CodeGenWithContext for Document { 9 | fn write_token(&self, cx: &CodeGenContext, output: &mut TokenStream) -> CodeGenResult<()> { 10 | let mut generated = TokenStream::new(); 11 | 12 | // generate include 13 | // We may not use includes of self since they are the file system path instead of 14 | // their namespace. 15 | // So the includes is set with CodeGenContext. 16 | for inc in cx.includes.iter() { 17 | let parts = inc 18 | .split("::") 19 | .map(|p| quote::format_ident!("{}", p)) 20 | .collect::>(); 21 | generated.extend(quote::quote! { 22 | pub use #(#parts)::*; 23 | }) 24 | } 25 | 26 | // generate struct 27 | for stut in self.structs.iter() { 28 | let _ = stut.write_token(&mut generated)?; 29 | } 30 | 31 | // generate service 32 | for service in self.services.iter() { 33 | let _ = service.write_token(&mut generated)?; 34 | } 35 | 36 | // generate namespaces, it will wrap the generated above. 37 | // We may not use namespaces of self since we only want to use scope rs or *. 38 | // Also, if no namespace exists, we want to use the file stem and self does not 39 | // know it. 40 | // So the namespace is set with CodeGenContext. 41 | for m in cx.namespaces.iter().rev() { 42 | let ident = quote::format_ident!("{}", m); 43 | generated = quote::quote! { 44 | pub mod #ident { 45 | #generated 46 | } 47 | } 48 | } 49 | // write to output 50 | output.extend(generated); 51 | Ok(()) 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use thrift_parser::document::Document; 58 | use thrift_parser::Parser; 59 | 60 | use crate::code_gen::{CodeGenContext, CodeGenWithContext}; 61 | 62 | #[test] 63 | fn test_namespace() { 64 | let doc = Document::default(); 65 | let cx = CodeGenContext { 66 | includes: vec![], 67 | namespaces: vec!["a".to_string(), "b".to_string(), "c".to_string()], 68 | }; 69 | 70 | // pub mod a { 71 | // pub mod b { 72 | // pub mod c {} 73 | // } 74 | // } 75 | assert_eq!( 76 | doc.gen_token(&cx).unwrap().to_string(), 77 | "pub mod a { pub mod b { pub mod c { } } }" 78 | ); 79 | } 80 | 81 | #[test] 82 | fn test_include() { 83 | let doc = Document::default(); 84 | let cx = CodeGenContext { 85 | includes: vec!["a::b::c".to_string(), "c::d::e".to_string()], 86 | namespaces: vec![], 87 | }; 88 | 89 | // pub use a::b::c; 90 | // pub use c::d::e; 91 | assert_eq!( 92 | doc.gen_token(&cx).unwrap().to_string(), 93 | "pub use a :: b :: c ; pub use c :: d :: e ;" 94 | ); 95 | } 96 | 97 | #[test] 98 | fn test_struct() { 99 | let doc = Document::parse( 100 | "struct MyBook { 1: string author, 2: i32 price } service TestSvc { void GetName(); }", 101 | ) 102 | .unwrap() 103 | .1; 104 | let cx = CodeGenContext::default(); 105 | println!("{}", doc.gen_token(&cx).unwrap().to_string()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/code_gen/errors.rs: -------------------------------------------------------------------------------- 1 | pub type CodeGenResult = Result; 2 | 3 | #[derive(thiserror::Error, Debug)] 4 | pub enum CodeGenError {} 5 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/code_gen/field.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | 4 | use thrift_parser::field::Field; 5 | use thrift_parser::types::FieldType; 6 | 7 | use crate::code_gen::errors::CodeGenResult; 8 | use crate::code_gen::{CodeGenWithContext, CodeGenContext, IdentifierGen, CodeGen, FieldGen}; 9 | use thrift_parser::constant::IntConstant; 10 | 11 | trait FormatFieldType { 12 | fn format(&self) -> String; 13 | } 14 | 15 | impl FormatFieldType for FieldType { 16 | fn format(&self) -> String { 17 | match self { 18 | FieldType::String => "string".to_string(), 19 | FieldType::Bool => "bool".to_string(), 20 | FieldType::I8 => "i8".to_string(), 21 | FieldType::I16 => "i16".to_string(), 22 | FieldType::I32 => "i32".to_string(), 23 | FieldType::I64 => "i64".to_string(), 24 | FieldType::Double => "double".to_string(), 25 | FieldType::Byte => "byte".to_string(), 26 | FieldType::Identifier(ident) => { 27 | format!("ident({})", ident.as_str()) 28 | } 29 | FieldType::List(inner) => { 30 | format!("list({})", inner.format()) 31 | } 32 | FieldType::Map(k, v) => { 33 | format!("map({}, {})", k.format(), v.format()) 34 | } 35 | FieldType::Set(inner) => { 36 | format!("set({})", inner.format()) 37 | } 38 | FieldType::Binary => "binary".to_string(), 39 | } 40 | } 41 | } 42 | 43 | impl FieldGen for Field { 44 | fn gen_for_struct(&self) -> CodeGenResult { 45 | let name = self.name.field_name()?; 46 | let mut type_ = self.type_.gen_token()?; 47 | if self.required != Some(true) { 48 | type_ = quote::quote! { ::std::option::Option<#type_> } 49 | } 50 | 51 | let annotation_id = self.id.map(|x| x.into_inner()).unwrap_or(0) as i16; 52 | let annotation_type = self.type_.format(); 53 | let annotation = match self.required { 54 | None => quote::quote! { 55 | #[mini_lust(field_id = #annotation_id, field_type = #annotation_type)] 56 | }, 57 | Some(true) => quote::quote! { 58 | #[mini_lust(field_id = #annotation_id, required = "true", field_type = #annotation_type)] 59 | }, 60 | Some(false) => quote::quote! { 61 | #[mini_lust(field_id = #annotation_id, required = "false", field_type = #annotation_type)] 62 | } 63 | }; 64 | 65 | Ok(quote::quote! { 66 | #annotation 67 | pub #name: #type_, 68 | }) 69 | } 70 | 71 | fn gen_name_type(&self, is_encode: bool) -> CodeGenResult { 72 | let name = self.name.field_name()?; 73 | let mut type_ = self.type_.gen_token()?; 74 | 75 | match self.required { 76 | Some(false) => { type_ = quote::quote! { ::std::option::Option<#type_> }; }, 77 | None if !is_encode => { type_ = quote::quote! { ::std::option::Option<#type_> }; }, 78 | _ => {} 79 | } 80 | 81 | Ok(quote::quote! { 82 | #name: #type_, 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/code_gen/functions.rs: -------------------------------------------------------------------------------- 1 | use thrift_parser::functions::Function; 2 | use crate::code_gen::{CodeGenContext, FunctionGen, CodeGen, FieldGen}; 3 | use proc_macro2::{TokenStream, Ident}; 4 | use crate::code_gen::errors::CodeGenResult; 5 | use thrift_parser::types::FieldType; 6 | 7 | impl FunctionGen for Function { 8 | // Generate struct AnonymousServiceCallArgs and AnonymousServiceCallResult 9 | fn anonymous(&self, service_ident: &Ident) -> CodeGenResult { 10 | let mut output = TokenStream::new(); 11 | 12 | // Generate struct AnonymousServiceCallArgs 13 | let struct_name = quote::format_ident!("Anonymous{}{}Args", service_ident, quote::format_ident!("{}", self.name.clone().into_inner())); 14 | let mut fields = Vec::with_capacity(self.parameters.len()); 15 | for field in self.parameters.iter() { 16 | fields.push(field.gen_for_struct()?) 17 | } 18 | 19 | output.extend(quote::quote! { 20 | #[derive(::mini_lust_macros::Message, Debug, Clone, PartialEq)] 21 | pub struct #struct_name { 22 | #(#fields)* 23 | } 24 | impl ::mini_lust_chap6::OrigType for #struct_name {} 25 | }); 26 | 27 | // Generate enum AnonymousServiceCallResult 28 | if !self.oneway { 29 | let enum_name = quote::format_ident!("Anonymous{}{}Result", service_ident, quote::format_ident!("{}", self.name.clone().into_inner())); 30 | let ret = self.returns.gen_token()?; 31 | 32 | let mut exps = Vec::new(); 33 | for exception in self.exceptions.as_ref().iter().flat_map(|exps| exps.iter()) { 34 | let name = quote::format_ident!("{}", exception.name.clone().into_inner()); 35 | let field_id = exception.id.expect("exception id is required").into_inner() as i16; 36 | let exp_type = exception.type_.gen_token()?; 37 | exps.push(quote::quote! { 38 | #[mini_lust(field_id = #field_id)] 39 | #name(#exp_type), 40 | }); 41 | } 42 | 43 | output.extend(quote::quote! { 44 | #[derive(::mini_lust_macros::Message, Debug, Clone, PartialEq)] 45 | pub enum #enum_name { 46 | #[mini_lust(field_id = 1)] 47 | Success(#ret), 48 | // User defined exceptions here 49 | #(#exps)* 50 | } 51 | impl ::mini_lust_chap6::OrigType for #enum_name {} 52 | }); 53 | } 54 | 55 | Ok(output) 56 | } 57 | 58 | // pub async fn get_user( 59 | // &mut self, 60 | // req: GetUserRequest, 61 | // shuffle: bool, 62 | // ) -> ::mini_lust_chap6::Result { 63 | // let anonymous_request = 64 | // AnonymousItemServiceRequest::GetUser(AnonymousItemServiceGetUserArgs { 65 | // req: Some(req), 66 | // shuffle: Some(shuffle), 67 | // }); 68 | // let resp = self.inner_client.call("GetUser", anonymous_request).await?; 69 | // 70 | // #[allow(irrefutable_let_patterns)] 71 | // if let AnonymousItemServiceResponse::GetUser(r) = resp { 72 | // return Ok(r); 73 | // } 74 | // Err(::mini_lust_chap6::new_application_error( 75 | // ::mini_lust_chap6::ApplicationErrorKind::Unknown, 76 | // "unable to get response", 77 | // )) 78 | // } 79 | fn impl_for_client(&self, service_ident: &Ident) -> CodeGenResult { 80 | let anonymous_args = quote::format_ident!("Anonymous{}{}Args", service_ident, self.name.clone().into_inner()); 81 | let anonymous_result = quote::format_ident!("Anonymous{}{}Result", service_ident, self.name.clone().into_inner()); 82 | let anonymous_request = quote::format_ident!("Anonymous{}Request", service_ident); 83 | let anonymous_response = quote::format_ident!("Anonymous{}Response", service_ident); 84 | let func_name = quote::format_ident!("{}", self.name.clone().into_inner()); 85 | let func_name_string = self.name.clone().into_inner(); 86 | 87 | let mut named_parameters = Vec::new(); 88 | let mut assignments = Vec::new(); 89 | for field in self.parameters.iter() { 90 | named_parameters.push(field.gen_name_type(true)?); 91 | let field_name = quote::format_ident!("{}", field.name.clone().into_inner()); 92 | if field.required == None { 93 | assignments.push(quote::quote! { #field_name: Some(#field_name), }) 94 | } else { 95 | assignments.push(quote::quote! { #field_name, }) 96 | } 97 | } 98 | 99 | // TODO: call or oneway 100 | Ok(quote::quote! { 101 | pub async fn #func_name( 102 | &mut self, 103 | #(#named_parameters)* 104 | ) -> ::mini_lust_chap6::Result<#anonymous_result> { 105 | let anonymous_request = 106 | #anonymous_request::GetUser(#anonymous_args { 107 | #(#assignments)* 108 | }); 109 | let resp = self.inner_client.call(#func_name_string, anonymous_request).await?; 110 | 111 | #[allow(irrefutable_let_patterns)] 112 | if let #anonymous_response::#func_name(r) = resp { 113 | return Ok(r); 114 | } 115 | Err(::mini_lust_chap6::new_application_error( 116 | ::mini_lust_chap6::ApplicationErrorKind::Unknown, 117 | "unable to get response", 118 | )) 119 | } 120 | }) 121 | } 122 | 123 | // async fn get_user( 124 | // &self, 125 | // req: Option, 126 | // shuffle: Option, 127 | // ) -> ::mini_lust_chap6::ApplicationResult; 128 | fn fn_for_trait(&self, service_ident: &Ident) -> CodeGenResult { 129 | let mut fields = Vec::new(); 130 | for field in self.parameters.iter() { 131 | fields.push(field.gen_name_type(false)?); 132 | } 133 | let anonymous_result = quote::format_ident!("Anonymous{}{}Result", service_ident, self.name.clone().into_inner()); 134 | let func_name = quote::format_ident!("{}", self.name.clone().into_inner()); 135 | Ok(quote::quote! { 136 | async fn #func_name( 137 | &self, 138 | #(#fields)* 139 | ) -> ::mini_lust_chap6::ApplicationResult<#anonymous_result>; 140 | }) 141 | } 142 | 143 | // Ok(AnonymousItemServiceRequest::GetUser(r)) => { 144 | // let ret = inner.get_user(r.req, r.shuffle).await; 145 | // match ret { 146 | // Ok(r) => { 147 | // cx.identifier.message_type = ::mini_lust_chap6::TMessageType::Reply; 148 | // Ok(Some((cx, Ok(AnonymousItemServiceResponse::GetUser(r))))) 149 | // } 150 | // Err(e) => { 151 | // cx.identifier.message_type = ::mini_lust_chap6::TMessageType::Exception; 152 | // Ok(Some((cx, Err(e)))) 153 | // } 154 | // } 155 | // } 156 | fn server_match_arm(&self, service_ident: &Ident) -> CodeGenResult { 157 | let anonymous_request = quote::format_ident!("Anonymous{}Request", service_ident); 158 | let anonymous_response = quote::format_ident!("Anonymous{}Response", service_ident); 159 | let func_name = quote::format_ident!("{}", self.name.clone().into_inner()); 160 | let r_parameters = self.parameters.iter().map(|f| { 161 | let name = quote::format_ident!("{}", f.name.clone().into_inner()); 162 | quote::quote! { r.#name } 163 | }); 164 | 165 | if self.oneway { 166 | return Ok(quote::quote! { 167 | Ok(#anonymous_request::#func_name(r)) => { 168 | let ret = inner.#func_name(#(#r_parameters),*).await; 169 | Ok(None) 170 | } 171 | }); 172 | } 173 | Ok(quote::quote! { 174 | Ok(#anonymous_request::#func_name(r)) => { 175 | let ret = inner.#func_name(#(#r_parameters),*).await; 176 | match ret { 177 | Ok(r) => { 178 | cx.identifier.message_type = ::mini_lust_chap6::TMessageType::Reply; 179 | Ok(Some((cx, Ok(#anonymous_response::#func_name(r))))) 180 | } 181 | Err(e) => { 182 | cx.identifier.message_type = ::mini_lust_chap6::TMessageType::Exception; 183 | Ok(Some((cx, Err(e)))) 184 | } 185 | } 186 | } 187 | }) 188 | } 189 | } -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/code_gen/header.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/chapter6/mini-lust-generator/src/code_gen/header.rs -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/code_gen/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{TokenStream, Ident}; 2 | 3 | use crate::code_gen::errors::CodeGenResult; 4 | 5 | mod basic; 6 | mod definition; 7 | mod document; 8 | pub mod errors; 9 | mod field; 10 | mod types; 11 | mod functions; 12 | 13 | #[derive(Default)] 14 | pub struct CodeGenContext { 15 | // namespaces of all files that it contains, eg: a::b::c, c::d::e 16 | includes: Vec, 17 | // namespaces, eg: a::b::C -> [a, b, c] 18 | namespaces: Vec, 19 | } 20 | 21 | impl CodeGenContext { 22 | pub fn new(mut includes: Vec, namespaces: String) -> Self { 23 | includes 24 | .iter_mut() 25 | .for_each(|item| *item = item.replace(".", "::")); 26 | let namespaces = namespaces.split("::").map(|x| x.to_string()).collect(); 27 | Self { 28 | includes, 29 | namespaces, 30 | } 31 | } 32 | } 33 | 34 | pub trait CodeGenWithContext { 35 | fn gen_token(&self, cx: &CodeGenContext) -> CodeGenResult { 36 | let mut stream = TokenStream::new(); 37 | let _ = self.write_token(cx, &mut stream)?; 38 | Ok(stream) 39 | } 40 | fn write_token(&self, cx: &CodeGenContext, output: &mut TokenStream) -> CodeGenResult<()>; 41 | } 42 | 43 | pub trait CodeGen { 44 | fn gen_token(&self) -> CodeGenResult { 45 | let mut stream = TokenStream::new(); 46 | let _ = self.write_token(&mut stream)?; 47 | Ok(stream) 48 | } 49 | fn write_token(&self, output: &mut TokenStream) -> CodeGenResult<()>; 50 | } 51 | 52 | pub trait IdentifierGen { 53 | fn field_name(&self) -> CodeGenResult; 54 | fn ident_name(&self) -> CodeGenResult; 55 | fn struct_name(&self) -> CodeGenResult; 56 | } 57 | 58 | pub trait FunctionGen { 59 | fn anonymous(&self, service_ident: &Ident) -> CodeGenResult; 60 | fn impl_for_client(&self, service_ident: &Ident) -> CodeGenResult; 61 | fn fn_for_trait(&self, service_ident: &Ident) -> CodeGenResult; 62 | fn server_match_arm(&self, service_ident: &Ident) -> CodeGenResult; 63 | } 64 | 65 | pub trait FieldGen { 66 | fn gen_for_struct(&self) -> CodeGenResult; 67 | fn gen_name_type(&self, is_encode: bool) -> CodeGenResult; 68 | } 69 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/code_gen/types.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | use thrift_parser::types::FieldType; 4 | 5 | use crate::code_gen::errors::CodeGenResult; 6 | use crate::code_gen::{CodeGen, CodeGenContext, CodeGenWithContext, IdentifierGen}; 7 | 8 | impl CodeGen for FieldType { 9 | fn write_token(&self, output: &mut TokenStream) -> CodeGenResult<()> { 10 | output.extend(match self { 11 | FieldType::Identifier(ident) => ident.struct_name()?, 12 | FieldType::Bool => quote::quote! { bool }, 13 | FieldType::Byte => quote::quote! { u8 }, 14 | FieldType::I8 => quote::quote! { i8 }, 15 | FieldType::I16 => quote::quote! { i16 }, 16 | FieldType::I32 => quote::quote! { i32 }, 17 | FieldType::I64 => quote::quote! { i64 }, 18 | FieldType::Double => quote::quote! { f64 }, 19 | FieldType::String => quote::quote! { ::std::string::String }, 20 | FieldType::Binary => quote::quote! { ::std::vec::Vec }, 21 | FieldType::Map(k, v) => { 22 | let k = k.gen_token()?; 23 | let v = v.gen_token()?; 24 | quote::quote! { ::std::collections::BTreeMap<#k, #v> } 25 | } 26 | FieldType::Set(v) => { 27 | let v = v.gen_token()?; 28 | quote::quote! { ::std::collections::BTreeSet<#v> } 29 | } 30 | FieldType::List(v) => { 31 | let v = v.gen_token()?; 32 | quote::quote! { ::std::vec::Vec<#v> } 33 | } 34 | }); 35 | Ok(()) 36 | } 37 | } 38 | 39 | impl CodeGen for Option { 40 | fn write_token(&self, output: &mut TokenStream) -> CodeGenResult<()> { 41 | match self.as_ref() { 42 | None => output.extend(quote::quote! { () }), 43 | Some(t) => { 44 | t.write_token(output); 45 | } 46 | } 47 | Ok(()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use crate::code_gen::errors::CodeGenError; 3 | 4 | pub type GenerateResult = Result; 5 | 6 | #[derive(thiserror::Error, Debug)] 7 | pub enum GenerateError { 8 | #[error("io error for path {path:?}: {err:?}")] 9 | Io{ 10 | err: std::io::Error, 11 | path: PathBuf, 12 | }, 13 | #[error("invalid file path")] 14 | Path(#[from] std::path::StripPrefixError), 15 | #[error("parse failed: {0}")] 16 | Parse(String), 17 | #[error("code generate error: {0}")] 18 | CodeGen(#[from] CodeGenError), 19 | #[error("dep loop")] 20 | Loop(), 21 | #[error("env error")] 22 | Env(#[from] std::env::VarError), 23 | #[error("unknown error: {0}")] 24 | Unknown(String) 25 | } 26 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/generator.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::io::Write; 3 | 4 | use proc_macro2::TokenStream; 5 | 6 | use crate::errors::{GenerateError, GenerateResult}; 7 | use crate::node::Node; 8 | 9 | pub struct Generator { 10 | // Input idls path 11 | idls: Vec, 12 | // Output file path 13 | output: Option, 14 | } 15 | 16 | impl Default for Generator { 17 | fn default() -> Self { 18 | Self::new() 19 | } 20 | } 21 | 22 | impl Generator { 23 | /// Create a generator 24 | pub fn new() -> Self { 25 | Self { 26 | idls: Default::default(), 27 | output: Default::default(), 28 | } 29 | } 30 | 31 | /// Add IDL file 32 | pub fn add_idl>(mut self, p: P) -> Self { 33 | self.idls.push(p.into()); 34 | self 35 | } 36 | 37 | /// Set output file path 38 | pub fn output>(mut self, p: P) -> Self { 39 | self.output = Some(p.into()); 40 | self 41 | } 42 | 43 | /// Generate all IDLs 44 | pub fn generate_token_stream(self) -> GenerateResult { 45 | let mut output = TokenStream::new(); 46 | let (mut generated, mut generating) = (HashSet::new(), Vec::new()); 47 | for idl in self.idls { 48 | let node = Node::new(&idl)?; 49 | let _ = node.generate(&mut generated, &mut generating, &mut output)?; 50 | } 51 | Ok(output) 52 | } 53 | 54 | /// Generate all IDLs and write to file 55 | pub fn generate(self) -> GenerateResult<()> { 56 | let output = self.output.clone(); 57 | 58 | // Generate token stream. 59 | let token_stream = self.generate_token_stream()?; 60 | 61 | // Get output file path form env or user specified. 62 | let output_file_path = match output { 63 | None => { 64 | // We will get OUT_DIR when build(It's a precise path). 65 | // However, in test the env not exists, so we use 66 | // ${CARGO_MANIFEST_DIR}/target(It's not a precise path). 67 | let output_dir = match std::env::var("OUT_DIR") { 68 | Ok(p) => p, 69 | Err(_) => std::env::var("CARGO_MANIFEST_DIR")? + "/target", 70 | }; 71 | let _ = std::fs::create_dir(&output_dir); 72 | let mut output_path = std::path::PathBuf::from(output_dir); 73 | output_path.push("generated.rs"); 74 | output_path 75 | } 76 | Some(p) => p, 77 | }; 78 | 79 | // Open output file. 80 | let mut output_file = 81 | std::fs::File::create(output_file_path.clone()).map_err(|err| GenerateError::Io { 82 | err, 83 | path: output_file_path.clone(), 84 | })?; 85 | 86 | // Write token stream to output file. 87 | output_file 88 | .write(token_stream.to_string().as_ref()) 89 | .map_err(|err| GenerateError::Io { 90 | err, 91 | path: output_file_path, 92 | })?; 93 | Ok(()) 94 | } 95 | } 96 | 97 | #[cfg(test)] 98 | mod tests { 99 | use crate::generator::Generator; 100 | 101 | #[test] 102 | fn test_gen() { 103 | let g = Generator::new(); 104 | let ts = g 105 | .add_idl("../mini-lust-generator/thrift/demo_base.thrift") 106 | .generate_token_stream() 107 | .unwrap(); 108 | println!("{}", ts.to_string()); 109 | } 110 | 111 | #[test] 112 | fn test_gen_demo() { 113 | let g = Generator::new(); 114 | let ts = g 115 | .add_idl("../mini-lust-generator/thrift/demo.thrift") 116 | .generate_token_stream() 117 | .unwrap(); 118 | println!("{}", ts.to_string()); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod code_gen; 2 | mod errors; 3 | mod generator; 4 | mod node; 5 | 6 | #[cfg(test)] 7 | mod tests { 8 | use std::str::FromStr; 9 | 10 | use thrift_parser::Parser; 11 | 12 | #[test] 13 | fn parse_idl() { 14 | let mut idl_path = 15 | std::path::PathBuf::from_str(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).unwrap(); 16 | idl_path.extend(vec!["thrift", "demo_base.thrift"]); 17 | let idl = std::fs::read_to_string(idl_path).unwrap(); 18 | let (remains, document) = thrift_parser::document::DocumentRef::parse(&idl).unwrap(); 19 | println!("Parser remains: {:?}, document: {:?}", remains, document); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/src/node.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::fs; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use heck::SnakeCase; 6 | use proc_macro2::TokenStream; 7 | 8 | use thrift_parser::document::Document; 9 | use thrift_parser::{Finish, Parser}; 10 | 11 | use crate::code_gen::{CodeGenWithContext, CodeGenContext}; 12 | use crate::errors::{GenerateError, GenerateResult}; 13 | 14 | /// Node represents a single IDL file and its meta info. 15 | #[derive(Debug, Clone)] 16 | pub struct Node { 17 | // document content, we read and parse it from file 18 | document: Document, 19 | // file_abs is IDL file abs path 20 | file_abs: PathBuf, 21 | // namespace is defined in IDL file like "a.b.c", if no namespace found, the 22 | // filename in snake case is used. 23 | namespace: String, 24 | } 25 | 26 | impl Node { 27 | /// Load node from file. 28 | pub fn new(file_path: &Path) -> GenerateResult { 29 | let file_abs = file_path.canonicalize().map_err(|e| GenerateError::Io { 30 | err: e, 31 | path: file_path.to_path_buf(), 32 | })?; 33 | 34 | let file_content = fs::read_to_string(&file_abs).map_err(|e| GenerateError::Io { 35 | err: e, 36 | path: file_abs.clone(), 37 | })?; 38 | let (_, document) = Document::parse(&file_content) 39 | .finish() 40 | .map_err(|e| GenerateError::Parse(format!("{}", e)))?; 41 | 42 | let (mut rs_namespace_ident, mut wildcard_namespace_ident) = (None, None); 43 | document 44 | .namespaces 45 | .iter() 46 | .for_each(|ns| match ns.scope.as_str() { 47 | "rs" => rs_namespace_ident = Some(ns.name.clone()), 48 | "*" => wildcard_namespace_ident = Some(ns.name.clone()), 49 | _ => {} 50 | }); 51 | 52 | let namespace = match (rs_namespace_ident, wildcard_namespace_ident) { 53 | (Some(ns), _) => ns.into_inner(), 54 | (None, Some(ns)) => ns.into_inner(), 55 | (None, None) => file_abs 56 | .file_stem() 57 | .ok_or_else(|| { 58 | GenerateError::Unknown(format!( 59 | "unable to extract file stem from {:?}", 60 | file_abs.clone() 61 | )) 62 | })? 63 | .to_string_lossy() 64 | .to_string() 65 | .to_snake_case(), 66 | }; 67 | 68 | Ok(Self { 69 | document, 70 | file_abs, 71 | namespace, 72 | }) 73 | } 74 | 75 | /// Generate token recursively to output. 76 | pub fn generate( 77 | &self, 78 | generated: &mut HashSet, 79 | generating: &mut Vec, 80 | output: &mut TokenStream, 81 | ) -> GenerateResult<()> { 82 | // Loop checking. 83 | if generating.contains(&self.file_abs) { 84 | // There must be some loops... 85 | return Err(GenerateError::Loop()); 86 | } 87 | 88 | // Bypass self if already generated. 89 | if generated.contains(&self.file_abs) { 90 | // We already generated this file! 91 | return Ok(()); 92 | } 93 | 94 | // Mark self as generating to prevent loop. 95 | generating.push(self.file_abs.clone()); 96 | 97 | let mut include_namespaces = Vec::with_capacity(self.document.includes.len()); 98 | for inc in self.document.includes.iter() { 99 | // Get includes path. 100 | let path = PathBuf::from(inc.clone().into_inner().into_inner()); 101 | let path_abs = if path.is_absolute() { 102 | path 103 | } else { 104 | self.file_abs.parent().unwrap().join(path) 105 | }; 106 | 107 | // Construct includes nodes. 108 | let node_next = Node::new(&path_abs)?; 109 | // Generate includes. 110 | node_next.generate(generated, generating, output)?; 111 | // Save includes namespaces. 112 | include_namespaces.push(node_next.namespace); 113 | } 114 | 115 | // Create CodeGenContext. 116 | let context = CodeGenContext::new(include_namespaces, self.namespace.clone()); 117 | // Generate document with the context. 118 | output.extend(self.document.gen_token(&context)?); 119 | 120 | // Moving self form generating to generated. 121 | generated.insert(generating.pop().unwrap()); 122 | Ok(()) 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use std::path::PathBuf; 129 | 130 | use super::*; 131 | 132 | #[test] 133 | fn test_new_node() { 134 | let file = PathBuf::from("../mini-lust-generator/thrift/demo_base.thrift".to_string()); 135 | let node = Node::new(&file).unwrap(); 136 | assert_eq!(node.namespace, "demo"); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/thrift/base.thrift: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-lust/tutorials/3dfe45dc99529a0a5eef1fbb2b817ae2ee9a0144/chapter6/mini-lust-generator/thrift/base.thrift -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/thrift/demo.thrift: -------------------------------------------------------------------------------- 1 | namespace rs demo 2 | 3 | struct User { 4 | 1: required i32 user_id, 5 | 2: required string user_name, 6 | 3: required bool is_male, 7 | 8 | 10: optional map extra, 9 | } 10 | 11 | struct GetUserRequest { 12 | 1: i32 user_id, 13 | 2: string user_name, 14 | 3: bool is_male, 15 | } 16 | 17 | struct GetUserResponse { 18 | 1: required list users, 19 | } 20 | 21 | service ItemService { 22 | GetUserResponse GetUser (1: GetUserRequest req, 2: bool shuffle), 23 | } -------------------------------------------------------------------------------- /chapter6/mini-lust-generator/thrift/demo_base.thrift: -------------------------------------------------------------------------------- 1 | namespace rs demo 2 | 3 | include "base.thrift" 4 | 5 | struct A { 6 | 1: required i32 user_id, 7 | 2: required string user_name, 8 | 3: required bool is_male, 9 | 10: optional map extra, 10 | } 11 | 12 | struct User { 13 | 1: required i32 user_id, 14 | 2: required string user_name, 15 | 3: required bool is_male, 16 | 17 | 10: optional map extra, 18 | } 19 | 20 | struct GetUserRequest { 21 | 1: i32 user_id, 22 | 2: string user_name, 23 | 3: bool is_male, 24 | } 25 | 26 | struct GetUserResponse { 27 | 1: required list users, 28 | } 29 | 30 | service ItemService { 31 | GetUserResponse GetUser (1: GetUserRequest req, 2: bool shuffle), 32 | } -------------------------------------------------------------------------------- /chapter6/mini-lust-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mini-lust-macros" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | darling = "0.13" 12 | syn = "1" 13 | proc-macro2 = "1" 14 | quote = "1" 15 | -------------------------------------------------------------------------------- /chapter6/mini-lust-macros/README.md: -------------------------------------------------------------------------------- 1 | # Mini Lust Macros 2 | Proc macro for mini-lust based on proc_macro2, darling and quote. 3 | 4 | Usage: 5 | ```rust 6 | use std::collections::BTreeMap; 7 | 8 | use mini_lust_chap6::{OrigType, TType}; 9 | 10 | #[derive(mini_lust_macros::Message)] 11 | pub struct Friend { 12 | #[mini_lust(field_id = 1, required = "true", field_type = "i32")] 13 | id: i32, 14 | } 15 | 16 | impl OrigType for Friend {} 17 | 18 | #[derive(mini_lust_macros::Message)] 19 | pub struct TestUser { 20 | #[mini_lust(field_id = 1, required = "true", field_type = "i32")] 21 | id: i32, 22 | #[mini_lust(field_id = 2, required = "true", field_type = "string")] 23 | name: String, 24 | #[mini_lust(field_id = 3, field_type = "map(string, list(string))")] 25 | accounts: Option>>, 26 | #[mini_lust(field_id = 4, required = "true", field_type = "list(ident(Friend))")] 27 | friends: Vec, 28 | } 29 | 30 | impl OrigType for TestUser {} 31 | 32 | #[derive(mini_lust_macros::Message)] 33 | #[mini_lust(dispatch_only = true)] 34 | pub enum MyArgs { 35 | MakeFriend(Friend), 36 | CreateTestUser(TestUser), 37 | } 38 | 39 | #[derive(mini_lust_macros::Message)] 40 | pub enum MyResult { 41 | #[mini_lust(field_id = 1)] 42 | Success(Friend), 43 | #[mini_lust(field_id = 2)] 44 | Exception(TestUser), 45 | } 46 | ``` -------------------------------------------------------------------------------- /chapter6/mini-lust-macros/src/fields.rs: -------------------------------------------------------------------------------- 1 | use crate::types::FieldType; 2 | use proc_macro2::{Ident, TokenStream}; 3 | 4 | pub(crate) fn encode_content(type_: &FieldType, ident: &Ident) -> TokenStream { 5 | match type_ { 6 | FieldType::String => quote::quote! { protocol.write_string(#ident)?; }, 7 | FieldType::Bool => quote::quote! { protocol.write_bool(*#ident)?; }, 8 | FieldType::I8 => quote::quote! { protocol.write_i8(*#ident)?; }, 9 | FieldType::I16 => quote::quote! { protocol.write_i16(*#ident)?; }, 10 | FieldType::I32 => quote::quote! { protocol.write_i32(*#ident)?; }, 11 | FieldType::I64 => quote::quote! { protocol.write_i64(*#ident)?; }, 12 | FieldType::Double => quote::quote! { protocol.write_double(*#ident)?; }, 13 | FieldType::Byte => quote::quote! { protocol.write_byte(*#ident)?; }, 14 | FieldType::Ident(_) => quote::quote! { #ident.encode(cx, protocol)?; }, 15 | FieldType::List(val) => { 16 | let inner = encode_content(val, "e::format_ident!("val")); 17 | quote::quote! { 18 | protocol.write_list_begin(&::mini_lust_chap6::TListIdentifier { 19 | element_type: #val, 20 | size: #ident.len() as i32, 21 | })?; 22 | for val in #ident.iter() { 23 | #inner 24 | } 25 | protocol.write_list_end()?; 26 | } 27 | } 28 | FieldType::Map(key, value) => { 29 | let key_inner = encode_content(key, "e::format_ident!("key")); 30 | let value_inner = encode_content(value, "e::format_ident!("value")); 31 | quote::quote! { 32 | protocol.write_map_begin(&::mini_lust_chap6::TMapIdentifier { 33 | key_type: Some(#key), 34 | value_type: Some(#value), 35 | size: #ident.len() as i32, 36 | })?; 37 | for (key, value) in #ident.iter() { 38 | #key_inner 39 | #value_inner 40 | } 41 | protocol.write_map_end()?; 42 | } 43 | } 44 | FieldType::Set(val) => { 45 | let inner = encode_content(val, "e::format_ident!("val")); 46 | quote::quote! { 47 | protocol.write_set_begin(&::mini_lust_chap6::TSetIdentifier { 48 | element_type: #val, 49 | size: #ident.len() as i32, 50 | })?; 51 | for val in #ident.iter() { 52 | #inner 53 | } 54 | protocol.write_set_end()?; 55 | } 56 | } 57 | FieldType::Void => { 58 | quote::quote! { ().encode(cx, protocol)?; } 59 | } 60 | FieldType::Binary => { 61 | quote::quote! { protocol.write_bytes(&#ident)?; } 62 | } 63 | } 64 | } 65 | 66 | pub(crate) fn decode_content(type_: &FieldType) -> TokenStream { 67 | match type_ { 68 | FieldType::String => quote::quote! { protocol.read_string()? }, 69 | FieldType::Bool => quote::quote! { protocol.read_bool()? }, 70 | FieldType::I8 => quote::quote! { protocol.read_i8()? }, 71 | FieldType::I16 => quote::quote! { protocol.read_i16()? }, 72 | FieldType::I32 => quote::quote! { protocol.read_i32()? }, 73 | FieldType::I64 => quote::quote! { protocol.read_i64()? }, 74 | FieldType::Double => quote::quote! { protocol.read_double()? }, 75 | FieldType::Byte => quote::quote! { protocol.read_byte()? }, 76 | FieldType::Ident(ident) => quote::quote! { #ident::decode(cx, protocol)? }, 77 | FieldType::List(val) => { 78 | let inner = decode_content(val); 79 | quote::quote! { 80 | { 81 | let list = protocol.read_list_begin()?; 82 | let mut val = ::std::vec::Vec::with_capacity(list.size as usize); 83 | for i in 0..list.size { 84 | let r_val = #inner; 85 | val.push(r_val); 86 | }; 87 | protocol.read_list_end()?; 88 | val 89 | } 90 | } 91 | }, 92 | FieldType::Map(key, value) => { 93 | let key = decode_content(key); 94 | let value = decode_content(value); 95 | quote::quote! { 96 | { 97 | let map = protocol.read_map_begin()?; 98 | let mut val = ::std::collections::BTreeMap::new(); 99 | for i in 0..map.size { 100 | let r_key = #key; 101 | let r_val = #value; 102 | val.insert(r_key, r_val); 103 | } 104 | protocol.read_map_end()?; 105 | val 106 | } 107 | } 108 | }, 109 | FieldType::Set(val) => { 110 | let inner = decode_content(val); 111 | quote::quote! { 112 | { 113 | let set = protocol.read_set_begin()?; 114 | let mut val = ::std::collections::BTreeSet::new(); 115 | for i in 0..set.size { 116 | let r_val = #inner; 117 | val.push(r_val); 118 | }; 119 | protocol.read_set_end()?; 120 | val 121 | } 122 | } 123 | }, 124 | FieldType::Void => { 125 | quote::quote! { 126 | { 127 | let _: () = lust_thrift::message::Message::decode(cx, protocol)?; 128 | () 129 | } 130 | } 131 | }, 132 | FieldType::Binary => quote::quote! { protocol.read_bytes()? } 133 | } 134 | } -------------------------------------------------------------------------------- /chapter6/mini-lust-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use darling::FromDeriveInput; 2 | use proc_macro2::TokenStream; 3 | 4 | use crate::receiver::{enum_to_message, fields_to_message, StructReceiver}; 5 | 6 | mod types; 7 | mod receiver; 8 | mod fields; 9 | 10 | #[proc_macro_derive(Message, attributes(mini_lust))] 11 | pub fn message(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 12 | let parsed = syn::parse_macro_input!(input as syn::DeriveInput); 13 | let receiver = StructReceiver::from_derive_input(&parsed).unwrap(); 14 | 15 | let name = receiver.ident; 16 | let generics = receiver.generics; 17 | 18 | let (tok_enc, tok_dec) = if receiver.data.is_struct() { 19 | let struct_stream = receiver.data.take_struct().unwrap(); 20 | fields_to_message(name.clone(), struct_stream.fields) 21 | } else if receiver.data.is_enum() { 22 | let enum_stream = receiver.data.take_enum().unwrap(); 23 | enum_to_message(name.clone(), enum_stream, receiver.dispatch_only) 24 | } else { 25 | (TokenStream::new(), TokenStream::new()) 26 | }; 27 | 28 | let ts2 = quote::quote! { 29 | impl ::mini_lust_chap6::Message for #name #generics { 30 | fn encode(&self, cx: &::mini_lust_chap6::MsgContext, protocol: &mut T) -> ::mini_lust_chap6::Result<()> { 31 | #tok_enc 32 | } 33 | 34 | fn decode(cx: &mut ::mini_lust_chap6::MsgContext, protocol: &mut T) -> ::mini_lust_chap6::Result { 35 | #tok_dec 36 | } 37 | } 38 | }; 39 | proc_macro::TokenStream::from(ts2) 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | #[test] 45 | fn test() {} 46 | } 47 | -------------------------------------------------------------------------------- /chapter6/mini-lust-macros/src/types.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, TokenStream}; 2 | use syn::spanned::Spanned; 3 | use syn::{Meta, NestedMeta}; 4 | use quote::ToTokens; 5 | 6 | #[derive(Debug)] 7 | pub(crate) enum FieldType { 8 | String, 9 | Bool, 10 | I8, 11 | I16, 12 | I32, 13 | I64, 14 | Double, 15 | Byte, 16 | Ident(Ident), 17 | List(Box), 18 | Map(Box, Box), 19 | Set(Box), 20 | Void, 21 | Binary, 22 | } 23 | 24 | impl ToTokens for FieldType { 25 | fn to_tokens(&self, tokens: &mut TokenStream) { 26 | tokens.extend(self.to_token_stream()); 27 | } 28 | } 29 | 30 | impl FieldType { 31 | pub fn to_token_stream(&self) -> TokenStream { 32 | match self { 33 | FieldType::String => { 34 | quote::quote! { ::mini_lust_chap6::TType::String } 35 | } 36 | FieldType::Bool => { 37 | quote::quote! { ::mini_lust_chap6::TType::Bool } 38 | } 39 | FieldType::I8 => { 40 | quote::quote! { ::mini_lust_chap6::TType::I8 } 41 | } 42 | FieldType::I16 => { 43 | quote::quote! { ::mini_lust_chap6::TType::I16 } 44 | } 45 | FieldType::I32 => { 46 | quote::quote! { ::mini_lust_chap6::TType::I32 } 47 | } 48 | FieldType::I64 => { 49 | quote::quote! { ::mini_lust_chap6::TType::I64 } 50 | } 51 | FieldType::Double => { 52 | quote::quote! { ::mini_lust_chap6::TType::Double } 53 | } 54 | FieldType::Byte => { 55 | quote::quote! { ::mini_lust_chap6::TType::Byte } 56 | } 57 | FieldType::Ident(ident) => { 58 | quote::quote! { { use mini_lust_chap6::OrigType; #ident::orig_type() }} 59 | } 60 | FieldType::List(_) => { 61 | quote::quote! { ::mini_lust_chap6::TType::List } 62 | } 63 | FieldType::Map(_, _) => { 64 | quote::quote! { ::mini_lust_chap6::TType::Map } 65 | } 66 | FieldType::Set(_) => { 67 | quote::quote! { ::mini_lust_chap6::TType::Set } 68 | } 69 | FieldType::Void => { 70 | quote::quote! { ::mini_lust_chap6::TType::Void } 71 | } 72 | FieldType::Binary => { 73 | quote::quote! { ::mini_lust_chap6::TType::String } 74 | } 75 | } 76 | } 77 | 78 | pub fn parse(s: &str) -> Result { 79 | let t: Meta = syn::parse_str(s)?; 80 | Self::parse_meta(&t) 81 | } 82 | 83 | fn parse_meta(m: &Meta) -> Result { 84 | let span = m.span().to_owned(); 85 | match m { 86 | Meta::Path(p) => match p.segments.first() { 87 | Some(seg) if seg.ident == "string" => Ok(Self::String), 88 | Some(seg) if seg.ident == "bool" => Ok(Self::Bool), 89 | Some(seg) if seg.ident == "i8" => Ok(Self::I8), 90 | Some(seg) if seg.ident == "i16" => Ok(Self::I16), 91 | Some(seg) if seg.ident == "i32" => Ok(Self::I32), 92 | Some(seg) if seg.ident == "i64" => Ok(Self::I64), 93 | Some(seg) if seg.ident == "double" => Ok(Self::Double), 94 | Some(seg) if seg.ident == "byte" => Ok(Self::Byte), 95 | Some(seg) if seg.ident == "void" => Ok(Self::Void), 96 | _ => { 97 | return Err(syn::Error::new(span, "")); 98 | } 99 | }, 100 | Meta::List(l) => match l.path.segments.first() { 101 | Some(seg) if seg.ident == "ident" => match l.nested.first() { 102 | Some(NestedMeta::Meta(Meta::Path(p))) => { 103 | if let Some(inner) = p.segments.first() { 104 | Ok(Self::Ident(inner.ident.clone())) 105 | } else { 106 | return Err(syn::Error::new(span, "")); 107 | } 108 | }, 109 | _ => { 110 | return Err(syn::Error::new(span, "")); 111 | } 112 | }, 113 | Some(seg) if seg.ident == "list" => match l.nested.first() { 114 | Some(NestedMeta::Meta(m)) => { 115 | let inner = Self::parse_meta(m)?; 116 | Ok(Self::List(Box::new(inner))) 117 | } 118 | _ => { 119 | return Err(syn::Error::new(span, "")); 120 | } 121 | }, 122 | Some(seg) if seg.ident == "set" => match l.nested.first() { 123 | Some(NestedMeta::Meta(m)) => { 124 | let inner = Self::parse_meta(m)?; 125 | Ok(Self::Set(Box::new(inner))) 126 | } 127 | _ => { 128 | return Err(syn::Error::new(span, "")); 129 | } 130 | }, 131 | Some(seg) if seg.ident == "map" => { 132 | match (l.nested.first(), l.nested.iter().nth(1)) { 133 | (Some(NestedMeta::Meta(k)), Some(NestedMeta::Meta(v))) => { 134 | let key = Self::parse_meta(k)?; 135 | let value = Self::parse_meta(v)?; 136 | Ok(Self::Map(Box::new(key), Box::new(value))) 137 | } 138 | _ => { 139 | return Err(syn::Error::new(span, "")); 140 | } 141 | } 142 | } 143 | _ => Err(syn::Error::new(span, "")), 144 | }, 145 | Meta::NameValue(_) => { 146 | return Err(syn::Error::new(span, "")); 147 | } 148 | } 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | mod tests { 154 | use super::*; 155 | 156 | #[test] 157 | fn test_parse() { 158 | let t: Meta = syn::parse_str("a(b(c, d))").unwrap(); 159 | println!("{:?}", t); 160 | let t: Meta = syn::parse_str("a(\"p\")").unwrap(); 161 | println!("{:?}", t); 162 | } 163 | 164 | #[test] 165 | fn test_parse_field_type() { 166 | println!("{:?}", FieldType::parse("string").unwrap()); 167 | println!("{:?}", FieldType::parse("ident(my_name)").unwrap()); 168 | println!("{:?}", FieldType::parse("map(string, byte)").unwrap()); 169 | println!("{:?}", FieldType::parse("list(i32)").unwrap()); 170 | println!( 171 | "{:?}", 172 | FieldType::parse("list(map(i8, map(i16, i32)))").unwrap() 173 | ); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /chapter6/mini-lust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mini-lust-chap6" 3 | version = "0.1.0" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | log = "0.4" 10 | bytes = "1.0" 11 | byteorder = "1.4" 12 | num_enum = "0.5" 13 | async-trait = "0.1" 14 | thiserror = "1.0" 15 | pin-project = "1.0" 16 | 17 | tokio-tower = "0.5" 18 | tokio-stream = { version = "0.1", features = ["net"] } 19 | tokio-util = { version = "0.6", features = ["codec"] } 20 | tower = { version = "0.4", features = ["make", "balance", "discover", "util", "limit", "buffer"] } 21 | futures-core = "0.3" 22 | futures = { version = "0.3", features = ["async-await", "std"] } 23 | futures-util = { version = "0.3", features = ["default", "sink"] } 24 | tokio = { version = "1", features = ["macros", "rt", "net", "rt-multi-thread"] } 25 | 26 | [dev-dependencies] 27 | tokio-test = "0.4" 28 | env_logger = "0.8" 29 | 30 | [features] 31 | default = [] 32 | unstable = [] 33 | -------------------------------------------------------------------------------- /chapter6/mini-lust/src/client.rs: -------------------------------------------------------------------------------- 1 | use std::task::{Context, Poll}; 2 | 3 | use futures::sink::SinkExt; 4 | use futures::stream::TryStreamExt; 5 | use tokio::io::{AsyncRead, AsyncWrite}; 6 | use tower::buffer::Buffer; 7 | use tower::util::BoxService; 8 | use tower::{Service, ServiceExt}; 9 | 10 | use crate::codec::MakeCodec; 11 | use crate::connection::SocketOrUnix; 12 | use crate::context::MsgContext; 13 | use crate::protocol::{TMessageIdentifier, TMessageType}; 14 | use crate::utils::BoxFuture; 15 | use crate::{ApplicationResult, DefaultMakeCodec, DefaultMakeConnection, FramedMakeTransport}; 16 | 17 | pub struct ClientBuilder { 18 | target: SocketOrUnix, 19 | make_codec: MCC, 20 | } 21 | 22 | impl ClientBuilder> { 23 | pub fn new(target: SocketOrUnix) -> Self { 24 | Self { 25 | target, 26 | make_codec: DefaultMakeCodec::new(), 27 | } 28 | } 29 | } 30 | 31 | impl ClientBuilder { 32 | pub fn make_codec(self, make_codec: MCC) -> Self { 33 | Self { 34 | target: self.target, 35 | make_codec, 36 | } 37 | } 38 | } 39 | 40 | const DEFAULT_BUFFER: usize = usize::MAX >> 3; 41 | 42 | impl ClientBuilder 43 | where 44 | Req: Send + 'static, 45 | Resp: Send + 'static, 46 | MCC: MakeCodec< 47 | EncodeItem = (MsgContext, ApplicationResult), 48 | DecodeItem = (MsgContext, ApplicationResult), 49 | Error = crate::Error, 50 | > + Send 51 | + 'static, 52 | MCC::Codec: Send + 'static, 53 | { 54 | pub fn build(self) -> Client { 55 | let make_connection = DefaultMakeConnection; 56 | let make_codec = self.make_codec; 57 | let transport_client = TransportClient::new(make_connection, make_codec); 58 | let inner = Buffer::new(BoxService::new(transport_client), DEFAULT_BUFFER); 59 | Client { 60 | inner, 61 | target: self.target, 62 | } 63 | } 64 | } 65 | 66 | #[derive(Clone)] 67 | pub struct Client { 68 | inner: Buffer< 69 | BoxService< 70 | (MsgContext, ApplicationResult), 71 | Option<(MsgContext, ApplicationResult)>, 72 | crate::Error, 73 | >, 74 | (MsgContext, ApplicationResult), 75 | >, 76 | target: SocketOrUnix, 77 | } 78 | 79 | impl Client { 80 | /// Call with method and Req and returns Result 81 | pub async fn call(&mut self, method: &'static str, req: Req) -> crate::Result { 82 | let context = MsgContext { 83 | identifier: TMessageIdentifier { 84 | name: method.to_string(), 85 | message_type: TMessageType::Call, 86 | 87 | ..TMessageIdentifier::default() 88 | }, 89 | target: Some(self.target.clone()), 90 | }; 91 | let req = (context, Ok(req)); 92 | // Option<(MsgContext, ApplicationResult)> 93 | let resp = self.inner.ready().await?.call(req).await?; 94 | resp.expect("returning resp is expected") 95 | .1 96 | .map_err(Into::into) 97 | } 98 | 99 | pub async fn oneway(&mut self, method: &'static str, req: Req) -> crate::Result<()> { 100 | let context = MsgContext { 101 | identifier: TMessageIdentifier { 102 | name: method.to_string(), 103 | message_type: TMessageType::OneWay, 104 | 105 | ..TMessageIdentifier::default() 106 | }, 107 | target: Some(self.target.clone()), 108 | }; 109 | let req = (context, Ok(req)); 110 | self.inner.ready().await?.call(req).await?; 111 | Ok(()) 112 | } 113 | } 114 | 115 | pub(crate) struct TransportClient { 116 | make_transport: FramedMakeTransport, 117 | seq_id: i32, 118 | } 119 | 120 | impl TransportClient { 121 | #[allow(unused)] 122 | pub fn new(make_connection: MCN, make_codec: MCC) -> Self { 123 | Self { 124 | make_transport: FramedMakeTransport::new(make_connection, make_codec), 125 | seq_id: 1, 126 | } 127 | } 128 | } 129 | 130 | /// Call with (MsgContext, ApplicationResult) returns 131 | /// Option<(MsgContext, ApplicationResult)>. 132 | impl Service<(MsgContext, ApplicationResult)> 133 | for TransportClient 134 | where 135 | MCN: Service + Clone + Send + 'static, 136 | MCN::Response: AsyncRead + AsyncWrite + Unpin + Send, 137 | MCN::Error: std::error::Error + Send + Sync + 'static, 138 | MCN::Future: Send, 139 | Req: Send + 'static, 140 | Resp: Send, 141 | MCC: MakeCodec< 142 | EncodeItem = (MsgContext, ApplicationResult), 143 | DecodeItem = (MsgContext, ApplicationResult), 144 | Error = crate::Error, 145 | >, 146 | MCC::Codec: Send + 'static, 147 | crate::Error: From<>::Error>, 148 | { 149 | // If oneway, the Response is None 150 | type Response = Option<(MsgContext, ApplicationResult)>; 151 | type Error = crate::Error; 152 | type Future = BoxFuture; 153 | 154 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 155 | self.make_transport.poll_ready(cx).map_err(Into::into) 156 | } 157 | 158 | fn call(&mut self, (mut cx, req): (MsgContext, ApplicationResult)) -> Self::Future { 159 | // TODO: use service discovery 160 | let target = cx 161 | .target 162 | .clone() 163 | .expect("unable to retrieve target from context"); 164 | let transport_fut = self.make_transport.call(target); 165 | 166 | self.seq_id += 1; 167 | cx.identifier.sequence_number = self.seq_id; 168 | let oneway = cx.identifier.message_type == TMessageType::OneWay; 169 | Box::pin(async move { 170 | let mut transport = transport_fut.await?; 171 | transport.send((cx, req)).await?; 172 | if oneway { 173 | return Ok(None); 174 | } 175 | transport.try_next().await.map_err(Into::into) 176 | }) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /chapter6/mini-lust/src/connection.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::SocketAddr; 3 | use std::path::PathBuf; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | 7 | use futures_util::FutureExt; 8 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 9 | use tower::Service; 10 | 11 | use crate::utils::BoxFuture; 12 | 13 | pub trait Io: AsyncWrite + AsyncRead + Send + 'static {} 14 | 15 | impl Io for T where T: AsyncRead + AsyncWrite + Send + 'static {} 16 | 17 | pub struct BoxedIo(Pin>); 18 | 19 | impl BoxedIo { 20 | pub fn new(io: I) -> BoxedIo { 21 | BoxedIo(Box::pin(io)) 22 | } 23 | } 24 | 25 | impl AsyncRead for BoxedIo { 26 | fn poll_read( 27 | mut self: Pin<&mut Self>, 28 | cx: &mut Context<'_>, 29 | buf: &mut ReadBuf<'_>, 30 | ) -> Poll> { 31 | Pin::new(&mut self.0).poll_read(cx, buf) 32 | } 33 | } 34 | 35 | impl AsyncWrite for BoxedIo { 36 | fn poll_write( 37 | mut self: Pin<&mut Self>, 38 | cx: &mut Context<'_>, 39 | buf: &[u8], 40 | ) -> Poll> { 41 | Pin::new(&mut self.0).poll_write(cx, buf) 42 | } 43 | 44 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 45 | Pin::new(&mut self.0).poll_flush(cx) 46 | } 47 | 48 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 49 | Pin::new(&mut self.0).poll_shutdown(cx) 50 | } 51 | } 52 | 53 | #[derive(Debug, Clone)] 54 | pub struct DefaultMakeConnection; 55 | 56 | #[derive(Debug, Clone, Eq, PartialEq)] 57 | pub enum SocketOrUnix { 58 | Socket(SocketAddr), 59 | #[cfg(unix)] 60 | Unix(PathBuf), 61 | } 62 | 63 | impl Service for DefaultMakeConnection { 64 | type Response = tokio::net::TcpStream; 65 | type Error = std::io::Error; 66 | type Future = BoxFuture; 67 | 68 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 69 | Poll::Ready(Ok(())) 70 | } 71 | 72 | fn call(&mut self, req: SocketAddr) -> Self::Future { 73 | Box::pin(tokio::net::TcpStream::connect(req)) 74 | } 75 | } 76 | 77 | #[cfg(unix)] 78 | impl Service for DefaultMakeConnection { 79 | type Response = tokio::net::UnixStream; 80 | type Error = std::io::Error; 81 | type Future = BoxFuture; 82 | 83 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 84 | Poll::Ready(Ok(())) 85 | } 86 | 87 | fn call(&mut self, req: PathBuf) -> Self::Future { 88 | Box::pin(tokio::net::UnixStream::connect(req)) 89 | } 90 | } 91 | 92 | impl Service for DefaultMakeConnection { 93 | type Response = BoxedIo; 94 | type Error = std::io::Error; 95 | type Future = BoxFuture; 96 | 97 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 98 | Poll::Ready(Ok(())) 99 | } 100 | 101 | fn call(&mut self, req: SocketOrUnix) -> Self::Future { 102 | match req { 103 | SocketOrUnix::Socket(addr) => { 104 | Box::pin(FutureExt::map(tokio::net::TcpStream::connect(addr), |r| { 105 | r.map(BoxedIo::new) 106 | })) 107 | } 108 | #[cfg(unix)] 109 | SocketOrUnix::Unix(path) => { 110 | Box::pin(FutureExt::map(tokio::net::UnixStream::connect(path), |r| { 111 | r.map(BoxedIo::new) 112 | })) 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /chapter6/mini-lust/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::protocol::TMessageIdentifier; 2 | use crate::connection::SocketOrUnix; 3 | 4 | /// MsgContext can only be used across our framework and middleware. 5 | #[derive(Clone, Debug, Default, Eq, PartialEq)] 6 | pub struct MsgContext { 7 | /// Thrift TMessageIdentifier 8 | pub identifier: TMessageIdentifier, 9 | /// target 10 | pub target: Option, 11 | } 12 | -------------------------------------------------------------------------------- /chapter6/mini-lust/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "unstable", feature(core_intrinsics))] 2 | 3 | pub use client::{Client, ClientBuilder}; 4 | pub use codec::DefaultMakeCodec; 5 | pub use connection::{DefaultMakeConnection, SocketOrUnix}; 6 | pub use context::MsgContext; 7 | // Export the error 8 | pub use errors::*; 9 | pub use message::Message; 10 | pub use protocol::{ 11 | TFieldIdentifier, TInputProtocol, TListIdentifier, TMapIdentifier, TMessageType, 12 | TOutputProtocol, TStructIdentifier, TType, 13 | }; 14 | pub use server::{Server, ServerError}; 15 | pub use transport::FramedMakeTransport; 16 | pub use types::OrigType; 17 | pub use utils::{ttype_comparing, BoxFuture}; 18 | 19 | pub type Result = std::result::Result; 20 | 21 | mod binary; 22 | mod client; 23 | mod codec; 24 | mod connection; 25 | mod context; 26 | mod errors; 27 | mod message; 28 | mod protocol; 29 | mod server; 30 | mod transport; 31 | mod types; 32 | mod utils; 33 | -------------------------------------------------------------------------------- /chapter6/mini-lust/src/message.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | use crate::context::MsgContext; 4 | use crate::protocol::{ 5 | TFieldIdentifier, TInputProtocol, TOutputProtocol, TStructIdentifier, TType, 6 | }; 7 | use crate::ApplicationError; 8 | 9 | pub trait Message: Sized { 10 | fn encode(&self, cx: &MsgContext, protocol: &mut T) -> crate::Result<()>; 11 | fn decode(cx: &mut MsgContext, protocol: &mut T) -> crate::Result; 12 | } 13 | 14 | /// ApplicationError defined as: 15 | /// exception TApplicationException { 16 | /// 1: string message, 17 | /// 2: i32 type 18 | /// } 19 | /// 20 | impl Message for ApplicationError { 21 | fn encode(&self, _cx: &MsgContext, protocol: &mut T) -> crate::Result<()> { 22 | protocol.write_struct_begin(&TStructIdentifier { 23 | name: "ApplicationError".to_string(), 24 | })?; 25 | protocol.write_field_begin(&TFieldIdentifier { 26 | name: Some("message".to_string()), 27 | field_type: TType::Struct, 28 | id: Some(1), 29 | })?; 30 | protocol.write_string(self.message.as_str())?; 31 | protocol.write_field_end()?; 32 | 33 | protocol.write_field_begin(&TFieldIdentifier { 34 | name: Some("type".to_string()), 35 | field_type: TType::I32, 36 | id: Some(2), 37 | })?; 38 | protocol.write_i32(self.kind.into())?; 39 | protocol.write_field_end()?; 40 | protocol.write_field_stop()?; 41 | protocol.write_struct_end()?; 42 | Ok(()) 43 | } 44 | 45 | fn decode(cx: &mut MsgContext, protocol: &mut T) -> crate::Result { 46 | protocol.read_struct_begin()?; 47 | let mut output = Self::default(); 48 | 49 | loop { 50 | let ident = protocol.read_field_begin()?; 51 | if ident.field_type == TType::Stop { 52 | break; 53 | } 54 | match ident.id { 55 | Some(1) => { 56 | // read string 57 | output.message = String::decode(cx, protocol)?; 58 | } 59 | Some(2) => { 60 | // read i32 61 | output.kind = protocol.read_i32()?.try_into()?; 62 | } 63 | _ => { 64 | protocol.skip(ident.field_type)?; 65 | } 66 | } 67 | protocol.read_field_end()?; 68 | } 69 | protocol.read_struct_end()?; 70 | Ok(output) 71 | } 72 | } 73 | 74 | impl Message for () { 75 | fn encode(&self, _cx: &MsgContext, protocol: &mut T) -> crate::Result<()> { 76 | protocol.write_struct_begin(&TStructIdentifier { 77 | name: "void".into(), 78 | })?; 79 | protocol.write_struct_end()?; 80 | Ok(()) 81 | } 82 | 83 | fn decode(_cx: &mut MsgContext, protocol: &mut T) -> crate::Result { 84 | protocol.read_struct_begin()?; 85 | protocol.read_struct_end()?; 86 | Ok(()) 87 | } 88 | } 89 | 90 | macro_rules! impl_message { 91 | ($e: ty, $r: ident, $w: ident) => { 92 | impl Message for $e { 93 | fn encode( 94 | &self, 95 | _cx: &MsgContext, 96 | protocol: &mut T, 97 | ) -> crate::Result<()> { 98 | protocol.$w(self)?; 99 | Ok(()) 100 | } 101 | 102 | fn decode( 103 | _cx: &mut MsgContext, 104 | protocol: &mut T, 105 | ) -> crate::Result { 106 | protocol.$r() 107 | } 108 | } 109 | }; 110 | } 111 | 112 | macro_rules! impl_message_deref { 113 | ($e: ty, $r: ident, $w: ident) => { 114 | impl Message for $e { 115 | fn encode( 116 | &self, 117 | _cx: &MsgContext, 118 | protocol: &mut T, 119 | ) -> crate::Result<()> { 120 | protocol.$w(*self)?; 121 | Ok(()) 122 | } 123 | 124 | fn decode( 125 | _cx: &mut MsgContext, 126 | protocol: &mut T, 127 | ) -> crate::Result { 128 | protocol.$r() 129 | } 130 | } 131 | }; 132 | } 133 | 134 | impl_message_deref!(bool, read_bool, write_bool); 135 | impl_message_deref!(i8, read_i8, write_i8); 136 | impl_message_deref!(i16, read_i16, write_i16); 137 | impl_message_deref!(i32, read_i32, write_i32); 138 | impl_message_deref!(i64, read_i64, write_i64); 139 | impl_message!(String, read_string, write_string); 140 | -------------------------------------------------------------------------------- /chapter6/mini-lust/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::marker::PhantomData; 3 | use std::net::SocketAddr; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | 7 | use futures::SinkExt; 8 | use futures_core::{ 9 | ready, 10 | stream::{Stream, TryStream}, 11 | }; 12 | use tokio::io::{AsyncRead, AsyncWrite}; 13 | use tokio::net::TcpStream; 14 | use tokio_stream::wrappers::TcpListenerStream; 15 | use tokio_stream::StreamExt; 16 | use tokio_util::codec::Framed; 17 | use tower::{Service, ServiceBuilder, ServiceExt}; 18 | 19 | use crate::codec::{DefaultMakeCodec, MakeCodec}; 20 | use crate::context::MsgContext; 21 | use crate::message::Message; 22 | use crate::protocol::TMessageType; 23 | use crate::{ApplicationError, ApplicationErrorKind, ApplicationResult}; 24 | 25 | #[async_trait::async_trait] 26 | pub trait Listenable { 27 | type Conn: AsyncRead + AsyncWrite + Send + Unpin + 'static; 28 | type Stream: Stream> + Unpin; 29 | 30 | async fn bind(&self) -> io::Result; 31 | } 32 | 33 | #[async_trait::async_trait] 34 | impl Listenable for SocketAddr { 35 | type Conn = TcpStream; 36 | type Stream = TcpListenerStream; 37 | 38 | async fn bind(&self) -> io::Result { 39 | let listener = tokio::net::TcpListener::bind(self).await?; 40 | Ok(TcpListenerStream::new(listener)) 41 | } 42 | } 43 | 44 | #[cfg(unix)] 45 | #[async_trait::async_trait] 46 | impl Listenable for std::path::PathBuf { 47 | type Conn = tokio::net::UnixStream; 48 | type Stream = tokio_stream::wrappers::UnixListenerStream; 49 | 50 | async fn bind(&self) -> io::Result { 51 | let listener = tokio::net::UnixListener::bind(self)?; 52 | Ok(tokio_stream::wrappers::UnixListenerStream::new(listener)) 53 | } 54 | } 55 | 56 | #[pin_project::pin_project] 57 | pub struct Incoming { 58 | #[pin] 59 | listener_stream: LS, 60 | make_codec: MC, 61 | } 62 | 63 | impl Incoming { 64 | #[allow(unused)] 65 | pub fn new(listener_stream: LS, make_codec: MC) -> Self { 66 | Self { 67 | listener_stream, 68 | make_codec, 69 | } 70 | } 71 | } 72 | 73 | impl Stream for Incoming 74 | where 75 | LS: TryStream, 76 | LS::Ok: AsyncRead + AsyncWrite, 77 | MC: MakeCodec, 78 | { 79 | type Item = Result, LS::Error>; 80 | 81 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 82 | let this = self.project(); 83 | let mut listener_stream = this.listener_stream; 84 | let codec = this.make_codec; 85 | match ready!(listener_stream.as_mut().try_poll_next(cx)) { 86 | Some(Ok(conn)) => { 87 | let f = Framed::new(conn, codec.make_codec()); 88 | Poll::Ready(Some(Ok(f))) 89 | } 90 | Some(Err(e)) => Poll::Ready(Some(Err(e))), 91 | None => Poll::Ready(None), 92 | } 93 | } 94 | } 95 | 96 | #[derive(thiserror::Error, Debug)] 97 | pub enum ServerError { 98 | #[error("IO error")] 99 | IO(#[from] io::Error), 100 | } 101 | 102 | const DEFAULT_BUFFER: usize = 1e3 as usize; // FIXME 103 | const DEFAULT_CONCURRENCY_LIMIT: usize = 1e3 as usize; // FIXME 104 | 105 | pub struct Server { 106 | concurrency_limit: Option, 107 | buffer: Option, 108 | inner: S, 109 | _marker: PhantomData, 110 | } 111 | 112 | impl Server 113 | where 114 | S: Service< 115 | (MsgContext, ApplicationResult), 116 | Response = Option<(MsgContext, ApplicationResult)>, 117 | Error = crate::Error, 118 | > + Send 119 | + 'static, 120 | S::Future: Send, 121 | { 122 | pub fn new(inner: S) -> Self { 123 | Self { 124 | concurrency_limit: None, 125 | buffer: None, 126 | inner, 127 | _marker: PhantomData, 128 | } 129 | } 130 | } 131 | 132 | impl Server 133 | where 134 | Addr: Listenable, 135 | S: Service< 136 | (MsgContext, ApplicationResult), 137 | Response = Option<(MsgContext, ApplicationResult)>, 138 | Error = crate::Error, 139 | > + Send 140 | + 'static, 141 | S::Future: Send, 142 | Req: Send + Message + 'static, 143 | Resp: Send + Message + 'static, 144 | { 145 | pub async fn serve(self, addr: Addr) -> Result<(), ServerError> { 146 | let listen_stream = addr.bind().await?; 147 | let make_codec = DefaultMakeCodec::::new(); 148 | let mut incoming = Incoming::new(listen_stream, make_codec); 149 | 150 | let buffer = self.buffer.unwrap_or(DEFAULT_BUFFER); 151 | let concurrency_limit = self.concurrency_limit.unwrap_or(DEFAULT_CONCURRENCY_LIMIT); 152 | let service = ServiceBuilder::new() 153 | .buffer(buffer) 154 | .concurrency_limit(concurrency_limit) 155 | .service(self.inner); 156 | 157 | loop { 158 | match incoming.try_next().await? { 159 | Some(mut ts) => { 160 | let mut service = service.clone(); 161 | tokio::spawn(async move { 162 | loop { 163 | match ts.try_next().await { 164 | Ok(Some(req)) => { 165 | let ready_service = match service.ready().await { 166 | Ok(svc) => svc, 167 | Err(e) => { 168 | log::error!("service not ready error: {:?}", e); 169 | return; 170 | } 171 | }; 172 | 173 | let mut cx = req.0.clone(); 174 | match ready_service.call(req).await { 175 | Ok(Some((cx, resp))) => { 176 | if let Err(e) = ts.send((cx, resp)).await { 177 | log::error!("send reply back error: {}", e); 178 | return; 179 | } 180 | } 181 | Ok(None) => { 182 | // oneway does not need response 183 | } 184 | Err(e) => { 185 | // if oneway, we just return 186 | if cx.identifier.message_type == TMessageType::OneWay { 187 | return; 188 | } 189 | // if not oneway, we must send the exception back 190 | cx.identifier.message_type = TMessageType::Exception; 191 | let app_error = ApplicationError::new( 192 | ApplicationErrorKind::Unknown, 193 | e.to_string(), 194 | ); 195 | if let Err(e) = ts.send((cx, Err(app_error))).await { 196 | log::error!("send error back error: {}", e); 197 | return; 198 | } 199 | } 200 | } 201 | } 202 | Ok(None) => { 203 | return; 204 | } 205 | Err(e) => { 206 | // receive message error 207 | log::error!("error receiving message {}", e); 208 | return; 209 | } 210 | } 211 | } 212 | }); 213 | } 214 | None => return Ok(()), 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /chapter6/mini-lust/src/transport.rs: -------------------------------------------------------------------------------- 1 | use std::task::{Context, Poll}; 2 | 3 | use tokio_util::codec::Framed; 4 | use tower::make::MakeConnection; 5 | use tower::Service; 6 | 7 | use crate::codec::MakeCodec; 8 | use crate::utils::BoxFuture; 9 | 10 | pub struct FramedMakeTransport { 11 | make_connection: MCN, 12 | make_codec: MCC, 13 | } 14 | 15 | impl FramedMakeTransport { 16 | #[allow(unused)] 17 | pub fn new(make_connection: MCN, make_codec: MCC) -> Self { 18 | Self { 19 | make_connection, 20 | make_codec, 21 | } 22 | } 23 | } 24 | 25 | impl Service for FramedMakeTransport 26 | where 27 | MCN: MakeConnection, 28 | MCN::Future: 'static + Send, 29 | MCC: MakeCodec, 30 | MCC::Codec: 'static + Send, 31 | { 32 | type Response = Framed; 33 | type Error = MCN::Error; 34 | type Future = BoxFuture; 35 | 36 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 37 | self.make_connection.poll_ready(cx) 38 | } 39 | 40 | fn call(&mut self, target: TG) -> Self::Future { 41 | let conn_fut = self.make_connection.make_connection(target); 42 | let codec = self.make_codec.make_codec(); 43 | Box::pin(async move { 44 | let conn = conn_fut.await?; 45 | Ok(Framed::new(conn, codec)) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /chapter6/mini-lust/src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::TType; 2 | 3 | pub trait OrigType { 4 | fn orig_type() -> crate::protocol::TType { 5 | TType::Struct 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /chapter6/mini-lust/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::pin::Pin; 3 | 4 | pub type BoxFuture = Pin> + Send>>; 5 | 6 | #[inline(always)] 7 | pub fn ttype_comparing(x: crate::protocol::TType, y: crate::protocol::TType) -> crate::Result<()> { 8 | if x != y { 9 | return Err(crate::errors::new_protocol_error( 10 | crate::errors::ProtocolErrorKind::InvalidData, 11 | format!("invalid ttype: {}, expect: {}", x, y), 12 | )); 13 | } 14 | Ok(()) 15 | } -------------------------------------------------------------------------------- /chapter6/mini-lust/thrift/demo.thrift: -------------------------------------------------------------------------------- 1 | namespace rust demo 2 | 3 | struct User { 4 | 1: required i32 user_id, 5 | 2: required string user_name, 6 | 3: required bool is_male, 7 | 8 | 10: optional map extra, 9 | } 10 | 11 | struct GetUserRequest { 12 | 1: i32 user_id, 13 | 2: string user_name, 14 | 3: bool is_male, 15 | } 16 | 17 | struct GetUserResponse { 18 | 1: required list users, 19 | } 20 | 21 | service ItemService { 22 | GetUserResponse GetUser (1: GetUserRequest req, 2: bool shuffle), 23 | } -------------------------------------------------------------------------------- /chapter6/thrift-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thrift-parser" 3 | version = "0.0.4" 4 | authors = ["ihciah "] 5 | edition = "2018" 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/ihciah/thrift-parser" 9 | description = "A simple thrift parser." 10 | keywords = ["thrift", "parser", "nom"] 11 | 12 | [dependencies] 13 | nom = "6" 14 | derive-newtype = "0.2" 15 | float-cmp = "0.8" 16 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/README.md: -------------------------------------------------------------------------------- 1 | # Thrift Parser 2 | 3 | Ref: https://crates.io/crates/thrift-parser -------------------------------------------------------------------------------- /chapter6/thrift-parser/examples/int_constant.rs: -------------------------------------------------------------------------------- 1 | //! This file is meant to illustrate how to use nom to parse. 2 | 3 | use std::str::FromStr; 4 | 5 | use nom::branch::alt; 6 | use nom::bytes::complete::{tag, take_while}; 7 | use nom::character::complete::{char as cchar, digit1, satisfy, space0}; 8 | use nom::combinator::{map, map_res, opt, recognize}; 9 | use nom::sequence::{preceded, tuple}; 10 | use nom::IResult; 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub struct IntConstant(pub i64); 14 | 15 | // Parse a int constant value like "+123", "123", "-123". 16 | // IntConstant ::= ('+' | '-')? Digit+ 17 | fn parse_int_constant(input: &str) -> IResult<&str, IntConstant> { 18 | map_res( 19 | recognize(tuple((opt(alt((tag("-"), tag("+")))), digit1))), 20 | |d_str| -> Result { 21 | let d = FromStr::from_str(d_str)?; 22 | Ok(IntConstant(d)) 23 | }, 24 | )(input) 25 | } 26 | 27 | #[derive(Debug, Clone)] 28 | pub struct Identifier(pub String); 29 | 30 | // Parse a identifier like "my_age", "my_salary", "my_name". 31 | // Identifier ::= ( Letter | '_' ) ( Letter | Digit | '.' | '_' )* 32 | // Note: Identifier is not strictly following the BNF above! 33 | // Instead, "_" and "_123" are not allowed since in rust they are invalid parameter names. 34 | fn parse_identifier(input: &str) -> IResult<&str, Identifier> { 35 | map( 36 | recognize(tuple(( 37 | opt(cchar('_')), 38 | satisfy(|c| c.is_ascii_alphabetic()), 39 | take_while(|c: char| c.is_ascii_alphanumeric() || c == '.' || c == '_'), 40 | ))), 41 | |ident: &str| -> Identifier { Identifier(ident.to_string()) }, 42 | )(input) 43 | } 44 | 45 | #[derive(Debug, Clone)] 46 | pub struct IntConstantExpr { 47 | pub name: Identifier, 48 | pub value: IntConstant, 49 | } 50 | 51 | // Parse a int const expr like "const my_age = +24", "const my_salary = 0". 52 | // Note: This is not thrift definition, it's just for demo. 53 | // IntConstant ::= 'const' Identifier '=' IntConstant 54 | fn parse_int_constant_expr(input: &str) -> IResult<&str, IntConstantExpr> { 55 | map( 56 | tuple(( 57 | tag("const"), 58 | preceded(space0, parse_identifier), 59 | preceded(space0, tag("=")), 60 | preceded(space0, parse_int_constant), 61 | )), 62 | |(_, name, _, value)| -> IntConstantExpr { IntConstantExpr { name, value } }, 63 | )(input) 64 | } 65 | 66 | #[cfg(test)] 67 | mod test { 68 | use super::*; 69 | 70 | #[test] 71 | fn test_identifier() { 72 | assert_eq!(parse_identifier("_ihc123iah,").unwrap().1 .0, "_ihc123iah"); 73 | assert_eq!(parse_identifier("ihc123iah,").unwrap().1 .0, "ihc123iah"); 74 | assert!(parse_identifier("_123").is_err()); 75 | assert!(parse_identifier("_").is_err()); 76 | assert!(parse_identifier("123").is_err()); 77 | } 78 | } 79 | 80 | fn main() { 81 | println!("{:?}", parse_int_constant("+123").unwrap().1); 82 | println!("{:?}", parse_identifier("_My.N1me").unwrap().1); 83 | println!("{:?}", parse_int_constant_expr("const aGe = +1").unwrap().1); 84 | } 85 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/examples/parse_document.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use thrift_parser::Parser; 4 | 5 | fn main() { 6 | let mut idl_path = 7 | std::path::PathBuf::from_str(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).unwrap(); 8 | idl_path.extend(vec!["thrift", "demo.thrift"]); 9 | let idl = std::fs::read_to_string(idl_path).unwrap(); 10 | let (remains, document) = thrift_parser::document::Document::parse(&idl).unwrap(); 11 | println!("Parser remains: {:?}, document: {:?}", remains, document); 12 | } 13 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/src/basic.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::{tag, take_till, take_until, take_while}; 3 | use nom::character::complete::{char as cchar, multispace1, one_of, satisfy}; 4 | use nom::combinator::{map, opt, recognize}; 5 | use nom::multi::many1; 6 | use nom::sequence::{delimited, preceded, tuple}; 7 | use nom::IResult; 8 | 9 | use crate::Parser; 10 | 11 | // Literal ::= ('"' [^"]* '"') | ("'" [^']* "'") 12 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 13 | pub struct LiteralRef<'a>(&'a str); 14 | 15 | impl<'a> Parser<'a> for LiteralRef<'a> { 16 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 17 | map( 18 | alt(( 19 | delimited(cchar('"'), take_until("\""), cchar('"')), 20 | delimited(cchar('\''), take_until("'"), cchar('\'')), 21 | )), 22 | Self, 23 | )(input) 24 | } 25 | } 26 | 27 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 28 | pub struct Literal(String); 29 | 30 | impl<'a> From> for Literal { 31 | fn from(r: LiteralRef<'a>) -> Self { 32 | Self(r.0.into()) 33 | } 34 | } 35 | 36 | impl<'a> Parser<'a> for Literal { 37 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 38 | LiteralRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 39 | } 40 | } 41 | 42 | // Identifier ::= ( Letter | '_' ) ( Letter | Digit | '.' | '_' )* 43 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 44 | pub struct IdentifierRef<'a>(&'a str); 45 | 46 | impl<'a> Parser<'a> for IdentifierRef<'a> { 47 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 48 | map( 49 | recognize(tuple(( 50 | opt(cchar('_')), 51 | satisfy(|c| c.is_ascii_alphabetic()), 52 | take_while(|c: char| c.is_ascii_alphanumeric() || c == '.' || c == '_'), 53 | ))), 54 | Self, 55 | )(input) 56 | } 57 | } 58 | 59 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 60 | pub struct Identifier(String); 61 | 62 | impl<'a> From> for Identifier { 63 | fn from(r: IdentifierRef<'a>) -> Self { 64 | Self(r.0.into()) 65 | } 66 | } 67 | 68 | impl<'a> Parser<'a> for Identifier { 69 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 70 | IdentifierRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 71 | } 72 | } 73 | 74 | // ListSeparator ::= ',' | ';' 75 | #[derive(Eq, PartialEq, Debug, Copy, Clone)] 76 | pub struct ListSeparator; 77 | 78 | impl<'a> Parser<'a> for ListSeparator { 79 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 80 | map(one_of(",;"), |_: char| Self)(input) 81 | } 82 | } 83 | 84 | // 1. The line begins with // or # 85 | // 2. The content between /* and */ 86 | #[derive(Eq, PartialEq, Debug, Clone)] 87 | pub struct CommentRef<'a>(&'a str); 88 | 89 | impl<'a> Parser<'a> for CommentRef<'a> { 90 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 91 | map( 92 | alt(( 93 | preceded(tag("//"), take_till(|c| c == '\n')), 94 | preceded(cchar('#'), take_till(|c| c == '\n')), 95 | delimited(tag("/*"), take_until("*/"), tag("*/")), 96 | )), 97 | Self, 98 | )(input) 99 | } 100 | } 101 | 102 | #[derive(Eq, PartialEq, Debug, Clone)] 103 | pub struct Comment(String); 104 | 105 | impl<'a> From> for Comment { 106 | fn from(r: CommentRef<'a>) -> Self { 107 | Self(r.0.into()) 108 | } 109 | } 110 | 111 | impl<'a> Parser<'a> for Comment { 112 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 113 | CommentRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 114 | } 115 | } 116 | 117 | // 1. Comment 118 | // 2. Space 119 | #[derive(Eq, PartialEq, Debug, Copy, Clone)] 120 | pub struct Separator; 121 | 122 | impl<'a> Parser<'a> for Separator { 123 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 124 | map( 125 | many1(alt(( 126 | map(CommentRef::parse, |_| ()), 127 | map(multispace1, |_| ()), 128 | ))), 129 | |_| Self, 130 | )(input) 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod test { 136 | use crate::utils::*; 137 | 138 | use super::*; 139 | 140 | #[test] 141 | fn test_literal() { 142 | assert_list_eq_with_f( 143 | vec![ 144 | "'ihciah'balabala", 145 | "'ihcia\"h'''''", 146 | "\"ihciah\"balabala", 147 | "\"ihcia'h\"''''", 148 | ], 149 | vec!["ihciah", "ihcia\"h", "ihciah", "ihcia'h"], 150 | LiteralRef::parse, 151 | LiteralRef, 152 | ); 153 | assert_list_err_with_f(vec!["'ihcia\"aa"], LiteralRef::parse); 154 | } 155 | 156 | #[test] 157 | fn test_identifier() { 158 | assert_list_eq_with_f( 159 | vec!["_ihc123iah,", "ihc123iah,"], 160 | vec!["_ihc123iah", "ihc123iah"], 161 | IdentifierRef::parse, 162 | IdentifierRef, 163 | ); 164 | assert_list_err_with_f(vec!["_123", "_", "123"], IdentifierRef::parse); 165 | } 166 | 167 | #[test] 168 | fn test_list_separator() { 169 | assert!(ListSeparator::parse(";").is_ok()); 170 | assert!(ListSeparator::parse(",").is_ok()); 171 | assert!(ListSeparator::parse("a").is_err()); 172 | } 173 | #[test] 174 | fn test_comment() { 175 | assert_list_eq_with_f( 176 | vec![ 177 | "//ihciah's #content", 178 | "//ihciah's #content balabala\nNextLine", 179 | "#ihciah's ///#content", 180 | "/*ihciah's con@#tent*///aaa", 181 | ], 182 | vec![ 183 | "ihciah's #content", 184 | "ihciah's #content balabala", 185 | "ihciah's ///#content", 186 | "ihciah's con@#tent", 187 | ], 188 | CommentRef::parse, 189 | CommentRef, 190 | ); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/src/document.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::combinator::{map, opt}; 3 | use nom::multi::many0; 4 | use nom::sequence::delimited; 5 | use nom::IResult; 6 | 7 | use crate::basic::Separator; 8 | use crate::definition::{ 9 | Const, ConstRef, Enum, EnumRef, Exception, ExceptionRef, Service, ServiceRef, Struct, 10 | StructRef, Typedef, TypedefRef, Union, UnionRef, 11 | }; 12 | use crate::header::{CppInclude, CppIncludeRef, Include, IncludeRef, Namespace, NamespaceRef}; 13 | use crate::Parser; 14 | 15 | #[derive(PartialEq, Debug, Clone, Default)] 16 | pub struct DocumentRef<'a> { 17 | pub includes: Vec>, 18 | pub cpp_includes: Vec>, 19 | pub namespaces: Vec>, 20 | pub typedefs: Vec>, 21 | pub consts: Vec>, 22 | pub enums: Vec>, 23 | pub structs: Vec>, 24 | pub unions: Vec>, 25 | pub exceptions: Vec>, 26 | pub services: Vec>, 27 | } 28 | 29 | impl<'a> Parser<'a> for DocumentRef<'a> { 30 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 31 | let mut target = Self::default(); 32 | let includes = &mut target.includes; 33 | let cpp_includes = &mut target.cpp_includes; 34 | let namespaces = &mut target.namespaces; 35 | let typedefs = &mut target.typedefs; 36 | let consts = &mut target.consts; 37 | let enums = &mut target.enums; 38 | let structs = &mut target.structs; 39 | let unions = &mut target.unions; 40 | let exceptions = &mut target.exceptions; 41 | let services = &mut target.services; 42 | 43 | let (remains, _) = many0(delimited( 44 | opt(Separator::parse), 45 | alt(( 46 | map(IncludeRef::parse, |i| includes.push(i)), 47 | map(CppIncludeRef::parse, |i| cpp_includes.push(i)), 48 | map(NamespaceRef::parse, |i| namespaces.push(i)), 49 | map(TypedefRef::parse, |i| typedefs.push(i)), 50 | map(ConstRef::parse, |i| consts.push(i)), 51 | map(EnumRef::parse, |i| enums.push(i)), 52 | map(StructRef::parse, |i| structs.push(i)), 53 | map(UnionRef::parse, |i| unions.push(i)), 54 | map(ExceptionRef::parse, |i| exceptions.push(i)), 55 | map(ServiceRef::parse, |i| services.push(i)), 56 | )), 57 | opt(Separator::parse), 58 | ))(input)?; 59 | Ok((remains, target)) 60 | } 61 | } 62 | 63 | #[derive(PartialEq, Debug, Clone, Default)] 64 | pub struct Document { 65 | pub includes: Vec, 66 | pub cpp_includes: Vec, 67 | pub namespaces: Vec, 68 | pub typedefs: Vec, 69 | pub consts: Vec, 70 | pub enums: Vec, 71 | pub structs: Vec, 72 | pub unions: Vec, 73 | pub exceptions: Vec, 74 | pub services: Vec, 75 | } 76 | 77 | impl<'a> From> for Document { 78 | fn from(r: DocumentRef<'a>) -> Self { 79 | Self { 80 | includes: r.includes.into_iter().map(Into::into).collect(), 81 | cpp_includes: r.cpp_includes.into_iter().map(Into::into).collect(), 82 | namespaces: r.namespaces.into_iter().map(Into::into).collect(), 83 | typedefs: r.typedefs.into_iter().map(Into::into).collect(), 84 | consts: r.consts.into_iter().map(Into::into).collect(), 85 | enums: r.enums.into_iter().map(Into::into).collect(), 86 | structs: r.structs.into_iter().map(Into::into).collect(), 87 | unions: r.unions.into_iter().map(Into::into).collect(), 88 | exceptions: r.exceptions.into_iter().map(Into::into).collect(), 89 | services: r.services.into_iter().map(Into::into).collect(), 90 | } 91 | } 92 | } 93 | 94 | impl<'a> Parser<'a> for Document { 95 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 96 | DocumentRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use crate::basic::LiteralRef; 103 | 104 | use super::*; 105 | 106 | #[test] 107 | fn test_document() { 108 | let expected = DocumentRef { 109 | includes: vec![IncludeRef::from(LiteralRef::from("another.thrift"))], 110 | ..Default::default() 111 | }; 112 | assert_eq!( 113 | DocumentRef::parse("include 'another.thrift'").unwrap().1, 114 | expected 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/src/field.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::tag; 3 | use nom::character::complete::char as cchar; 4 | use nom::combinator::{map, opt}; 5 | use nom::sequence::{delimited, terminated, tuple}; 6 | use nom::IResult; 7 | 8 | use crate::basic::{Identifier, IdentifierRef, ListSeparator, Separator}; 9 | use crate::constant::{ConstValue, ConstValueRef, IntConstant}; 10 | use crate::types::{FieldType, FieldTypeRef}; 11 | use crate::Parser; 12 | 13 | // Field ::= FieldID? FieldReq? FieldType Identifier ('=' ConstValue)? ListSeparator? 14 | // FieldID ::= IntConstant ':' 15 | // FieldReq ::= 'required' | 'optional' 16 | // Note: XsdFieldOptions is not supported in out impl and strongly discouraged in official docs. 17 | #[derive(Debug, Clone, PartialEq)] 18 | pub struct FieldRef<'a> { 19 | pub id: Option, 20 | pub required: Option, 21 | pub type_: FieldTypeRef<'a>, 22 | pub name: IdentifierRef<'a>, 23 | pub default: Option>, 24 | } 25 | 26 | impl<'a> Parser<'a> for FieldRef<'a> { 27 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 28 | map( 29 | tuple(( 30 | opt(terminated( 31 | IntConstant::parse, 32 | delimited(opt(Separator::parse), cchar(':'), opt(Separator::parse)), 33 | )), 34 | opt(terminated( 35 | alt(( 36 | map(tag("required"), |_| true), 37 | map(tag("optional"), |_| false), 38 | )), 39 | Separator::parse, 40 | )), 41 | terminated(FieldTypeRef::parse, Separator::parse), 42 | terminated(IdentifierRef::parse, opt(Separator::parse)), 43 | opt(map( 44 | tuple((cchar('='), opt(Separator::parse), ConstValueRef::parse)), 45 | |(_, _, cv)| cv, 46 | )), 47 | opt(Separator::parse), 48 | opt(ListSeparator::parse), 49 | )), 50 | |(id, required, type_, name, default, _, _)| Self { 51 | id, 52 | required, 53 | type_, 54 | name, 55 | default, 56 | }, 57 | )(input) 58 | } 59 | } 60 | 61 | #[derive(Debug, Clone, PartialEq)] 62 | pub struct Field { 63 | pub id: Option, 64 | pub required: Option, 65 | pub type_: FieldType, 66 | pub name: Identifier, 67 | pub default: Option, 68 | } 69 | 70 | impl<'a> From> for Field { 71 | fn from(r: FieldRef<'a>) -> Self { 72 | Self { 73 | id: r.id, 74 | required: r.required, 75 | type_: r.type_.into(), 76 | name: r.name.into(), 77 | default: r.default.map(Into::into), 78 | } 79 | } 80 | } 81 | 82 | impl<'a> Parser<'a> for Field { 83 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 84 | FieldRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod test { 90 | use crate::basic::LiteralRef; 91 | 92 | use super::*; 93 | 94 | #[test] 95 | fn test_field() { 96 | let expected = FieldRef { 97 | id: None, 98 | required: Some(true), 99 | type_: FieldTypeRef::String, 100 | name: IdentifierRef::from("name"), 101 | default: Some(ConstValueRef::Literal(LiteralRef::from("ihciah"))), 102 | }; 103 | assert_eq!( 104 | FieldRef::parse("required string name = 'ihciah'") 105 | .unwrap() 106 | .1, 107 | expected 108 | ); 109 | assert_eq!( 110 | FieldRef::parse("required string name='ihciah';").unwrap().1, 111 | expected 112 | ); 113 | 114 | let expected = FieldRef { 115 | id: Some(IntConstant::from(3)), 116 | required: Some(true), 117 | type_: FieldTypeRef::String, 118 | name: IdentifierRef::from("name"), 119 | default: Some(ConstValueRef::Literal(LiteralRef::from("ihciah"))), 120 | }; 121 | assert_eq!( 122 | FieldRef::parse("3 : required string name = 'ihciah'") 123 | .unwrap() 124 | .1, 125 | expected 126 | ); 127 | assert_eq!( 128 | FieldRef::parse("3:required string name='ihciah';") 129 | .unwrap() 130 | .1, 131 | expected 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/src/functions.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::tag; 3 | use nom::character::complete::char as cchar; 4 | use nom::combinator::{map, opt}; 5 | use nom::multi::separated_list0; 6 | use nom::sequence::{delimited, pair, preceded, terminated, tuple}; 7 | use nom::IResult; 8 | 9 | use crate::basic::{Identifier, IdentifierRef, ListSeparator, Separator}; 10 | use crate::field::{Field, FieldRef}; 11 | use crate::types::{FieldType, FieldTypeRef}; 12 | use crate::Parser; 13 | 14 | // Function ::= 'oneway'? FunctionType Identifier '(' Field* ')' Throws? ListSeparator? 15 | // FunctionType ::= FieldType | 'void' 16 | // Throws ::= 'throws' '(' Field* ')' 17 | #[derive(Debug, Clone, PartialEq)] 18 | pub struct FunctionRef<'a> { 19 | pub oneway: bool, 20 | // returns None means void 21 | pub returns: Option>, 22 | pub name: IdentifierRef<'a>, 23 | pub parameters: Vec>, 24 | pub exceptions: Option>>, 25 | } 26 | 27 | impl<'a> Parser<'a> for FunctionRef<'a> { 28 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 29 | map( 30 | tuple(( 31 | map(opt(terminated(tag("oneway"), Separator::parse)), |x| { 32 | x.is_some() 33 | }), 34 | terminated( 35 | alt((map(tag("void"), |_| None), map(FieldTypeRef::parse, Some))), 36 | Separator::parse, 37 | ), 38 | terminated(IdentifierRef::parse, opt(Separator::parse)), 39 | terminated( 40 | delimited( 41 | cchar('('), 42 | separated_list0(Separator::parse, FieldRef::parse), 43 | cchar(')'), 44 | ), 45 | opt(Separator::parse), 46 | ), 47 | opt(preceded( 48 | pair(tag("throws"), Separator::parse), 49 | delimited( 50 | cchar('('), 51 | separated_list0(Separator::parse, FieldRef::parse), 52 | cchar(')'), 53 | ), 54 | )), 55 | opt(pair(opt(Separator::parse), ListSeparator::parse)), 56 | )), 57 | |(oneway, returns, name, parameters, exceptions, _)| Self { 58 | oneway, 59 | returns, 60 | name, 61 | parameters, 62 | exceptions, 63 | }, 64 | )(input) 65 | } 66 | } 67 | 68 | #[derive(Debug, Clone, PartialEq)] 69 | pub struct Function { 70 | pub oneway: bool, 71 | // returns None means void 72 | pub returns: Option, 73 | pub name: Identifier, 74 | pub parameters: Vec, 75 | pub exceptions: Option>, 76 | } 77 | 78 | impl<'a> From> for Function { 79 | fn from(r: FunctionRef<'a>) -> Self { 80 | Self { 81 | oneway: r.oneway, 82 | returns: r.returns.map(Into::into), 83 | name: r.name.into(), 84 | parameters: r.parameters.into_iter().map(Into::into).collect(), 85 | exceptions: r 86 | .exceptions 87 | .map(|x| x.into_iter().map(Into::into).collect()), 88 | } 89 | } 90 | } 91 | 92 | impl<'a> Parser<'a> for Function { 93 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 94 | FunctionRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod test { 100 | use crate::basic::LiteralRef; 101 | use crate::constant::{ConstValueRef, IntConstant}; 102 | 103 | use super::*; 104 | 105 | #[test] 106 | fn test_function() { 107 | let expected = FunctionRef { 108 | oneway: false, 109 | returns: Some(FieldTypeRef::String), 110 | name: IdentifierRef::from("GetUser"), 111 | parameters: vec![FieldRef { 112 | id: None, 113 | required: Some(true), 114 | type_: FieldTypeRef::String, 115 | name: IdentifierRef::from("name"), 116 | default: Some(ConstValueRef::Literal(LiteralRef::from("ihciah"))), 117 | }], 118 | exceptions: None, 119 | }; 120 | assert_eq!( 121 | FunctionRef::parse("string GetUser(required string name='ihciah')") 122 | .unwrap() 123 | .1, 124 | expected 125 | ); 126 | 127 | let expected = FunctionRef { 128 | oneway: true, 129 | returns: None, 130 | name: IdentifierRef::from("DeleteUser"), 131 | parameters: vec![FieldRef { 132 | id: Some(IntConstant::from(10086)), 133 | required: Some(false), 134 | type_: FieldTypeRef::I32, 135 | name: IdentifierRef::from("age"), 136 | default: None, 137 | }], 138 | exceptions: None, 139 | }; 140 | assert_eq!( 141 | FunctionRef::parse("oneway void DeleteUser(10086:optional i32 age)") 142 | .unwrap() 143 | .1, 144 | expected 145 | ); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/src/header.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::tag; 3 | use nom::combinator::map; 4 | use nom::sequence::{pair, preceded, tuple}; 5 | use nom::IResult; 6 | 7 | use crate::basic::{Identifier, IdentifierRef, Literal, LiteralRef, Separator}; 8 | use crate::Parser; 9 | 10 | // Include ::= 'include' Literal 11 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 12 | pub struct IncludeRef<'a>(LiteralRef<'a>); 13 | 14 | impl<'a> Parser<'a> for IncludeRef<'a> { 15 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 16 | map( 17 | preceded(pair(tag("include"), Separator::parse), LiteralRef::parse), 18 | Self, 19 | )(input) 20 | } 21 | } 22 | 23 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 24 | pub struct Include(Literal); 25 | 26 | impl<'a> From> for Include { 27 | fn from(r: IncludeRef<'a>) -> Self { 28 | Self(r.0.into()) 29 | } 30 | } 31 | 32 | impl<'a> Parser<'a> for Include { 33 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 34 | IncludeRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 35 | } 36 | } 37 | 38 | // CppInclude ::= 'cpp_include' Literal 39 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 40 | pub struct CppIncludeRef<'a>(LiteralRef<'a>); 41 | 42 | impl<'a> Parser<'a> for CppIncludeRef<'a> { 43 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 44 | map( 45 | preceded( 46 | pair(tag("cpp_include"), Separator::parse), 47 | LiteralRef::parse, 48 | ), 49 | Self, 50 | )(input) 51 | } 52 | } 53 | 54 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 55 | pub struct CppInclude(Literal); 56 | 57 | impl<'a> From> for CppInclude { 58 | fn from(r: CppIncludeRef<'a>) -> Self { 59 | Self(r.0.into()) 60 | } 61 | } 62 | 63 | impl<'a> Parser<'a> for CppInclude { 64 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 65 | CppIncludeRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 66 | } 67 | } 68 | 69 | // Namespace ::= ( 'namespace' ( NamespaceScope Identifier ) ) 70 | #[derive(Eq, PartialEq, Debug, Clone)] 71 | pub struct NamespaceRef<'a> { 72 | pub scope: NamespaceScopeRef<'a>, 73 | pub name: IdentifierRef<'a>, 74 | } 75 | 76 | // NamespaceScope ::= '*' | 'c_glib' | 'rs' | 'cpp' | 'delphi' | 'haxe' | 'go' | 'java' | 77 | // 'js' | 'lua' | 'netstd' | 'perl' | 'php' | 'py' | 'py.twisted' | 'rb' | 'st' | 'xsd' 78 | // We add rust into it. 79 | // Ref: https://github.com/apache/thrift/blob/master/lib/rs/test_recursive/src/transit/Transporters.thrift 80 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 81 | pub struct NamespaceScopeRef<'a>(&'a str); 82 | 83 | impl<'a> Parser<'a> for NamespaceRef<'a> { 84 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 85 | map( 86 | tuple(( 87 | tag("namespace"), 88 | preceded(Separator::parse, NamespaceScopeRef::parse), 89 | preceded(Separator::parse, IdentifierRef::parse), 90 | )), 91 | |(_, scope, name)| Self { scope, name }, 92 | )(input) 93 | } 94 | } 95 | 96 | impl<'a> Parser<'a> for NamespaceScopeRef<'a> { 97 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 98 | map( 99 | alt(( 100 | tag("*"), 101 | tag("c_glib"), 102 | tag("rs"), 103 | tag("cpp"), 104 | tag("delphi"), 105 | tag("haxe"), 106 | tag("go"), 107 | tag("java"), 108 | tag("js"), 109 | tag("lua"), 110 | tag("netstd"), 111 | tag("perl"), 112 | tag("php"), 113 | tag("py"), 114 | tag("py.twisted"), 115 | tag("rb"), 116 | tag("st"), 117 | tag("xsd"), 118 | )), 119 | Self, 120 | )(input) 121 | } 122 | } 123 | 124 | #[derive(Eq, PartialEq, Debug, Clone)] 125 | pub struct Namespace { 126 | pub scope: NamespaceScope, 127 | pub name: Identifier, 128 | } 129 | 130 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 131 | pub struct NamespaceScope(String); 132 | 133 | impl<'a> From> for Namespace { 134 | fn from(r: NamespaceRef<'a>) -> Self { 135 | Self { 136 | scope: r.scope.into(), 137 | name: r.name.into(), 138 | } 139 | } 140 | } 141 | 142 | impl<'a> From> for NamespaceScope { 143 | fn from(r: NamespaceScopeRef<'a>) -> Self { 144 | Self(r.0.into()) 145 | } 146 | } 147 | 148 | impl<'a> Parser<'a> for Namespace { 149 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 150 | NamespaceRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 151 | } 152 | } 153 | 154 | impl<'a> Parser<'a> for NamespaceScope { 155 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 156 | NamespaceScopeRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use super::*; 163 | 164 | #[test] 165 | fn test_include() { 166 | assert_eq!( 167 | IncludeRef::parse("include 'another.thrift'").unwrap().1, 168 | IncludeRef::from(LiteralRef::from("another.thrift")) 169 | ) 170 | } 171 | 172 | #[test] 173 | fn test_namespace() { 174 | assert_eq!( 175 | NamespaceRef::parse("namespace * MyNamespace").unwrap().1, 176 | NamespaceRef { 177 | scope: NamespaceScopeRef::from("*"), 178 | name: IdentifierRef::from("MyNamespace") 179 | } 180 | ) 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use nom::Finish; 2 | use nom::IResult; 3 | pub use nom::{ 4 | error::{Error, ErrorKind}, 5 | Err, 6 | }; 7 | 8 | pub mod basic; 9 | pub mod constant; 10 | pub mod definition; 11 | pub mod document; 12 | pub mod field; 13 | pub mod functions; 14 | pub mod header; 15 | pub mod types; 16 | mod utils; 17 | 18 | pub trait Parser<'a>: Sized { 19 | fn parse(input: &'a str) -> IResult<&'a str, Self>; 20 | } 21 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/src/types.rs: -------------------------------------------------------------------------------- 1 | use nom::branch::alt; 2 | use nom::bytes::complete::tag; 3 | use nom::character::complete::char as cchar; 4 | use nom::combinator::{map, opt}; 5 | use nom::sequence::{delimited, pair, preceded, separated_pair, terminated, tuple}; 6 | use nom::IResult; 7 | 8 | use crate::basic::{Identifier, IdentifierRef, Literal, LiteralRef, Separator}; 9 | use crate::Parser; 10 | 11 | // FieldType ::= Identifier | BaseType | ContainerType 12 | // BaseType ::= 'bool' | 'byte' | 'i8' | 'i16' | 'i32' | 'i64' | 'double' | 'string' | 'binary' 13 | // ContainerType ::= MapType | SetType | ListType 14 | // MapType ::= 'map' CppType? '<' FieldType ',' FieldType '>' 15 | // SetType ::= 'set' CppType? '<' FieldType '>' 16 | // ListType ::= 'list' '<' FieldType '>' CppType? 17 | // CppType ::= 'cpp_type' Literal 18 | // Note: CppType is not fully supported in out impl. 19 | #[derive(Debug, Clone, PartialEq)] 20 | pub enum FieldTypeRef<'a> { 21 | Identifier(IdentifierRef<'a>), 22 | Bool, 23 | Byte, 24 | I8, 25 | I16, 26 | I32, 27 | I64, 28 | Double, 29 | String, 30 | Binary, 31 | Map(Box>, Box>), 32 | Set(Box>), 33 | List(Box>), 34 | } 35 | 36 | impl<'a> FieldTypeRef<'a> { 37 | pub fn parse_base_type(input: &'a str) -> IResult<&'a str, Self> { 38 | alt(( 39 | map(tag("bool"), |_| Self::Bool), 40 | map(tag("byte"), |_| Self::Byte), 41 | map(tag("i8"), |_| Self::I8), 42 | map(tag("i16"), |_| Self::I16), 43 | map(tag("i32"), |_| Self::I32), 44 | map(tag("i64"), |_| Self::I64), 45 | map(tag("double"), |_| Self::Double), 46 | map(tag("string"), |_| Self::String), 47 | map(tag("binary"), |_| Self::Binary), 48 | ))(input) 49 | } 50 | 51 | pub fn parse_container_type(input: &'a str) -> IResult<&'a str, Self> { 52 | alt(( 53 | map( 54 | preceded( 55 | tuple(( 56 | tag("map"), 57 | opt(Separator::parse), 58 | opt(terminated(CppTypeRef::parse, opt(Separator::parse))), 59 | )), 60 | delimited( 61 | pair(cchar('<'), opt(Separator::parse)), 62 | separated_pair( 63 | FieldTypeRef::parse, 64 | tuple((opt(Separator::parse), cchar(','), opt(Separator::parse))), 65 | FieldTypeRef::parse, 66 | ), 67 | pair(opt(Separator::parse), cchar('>')), 68 | ), 69 | ), 70 | |(k, v)| Self::Map(Box::new(k), Box::new(v)), 71 | ), 72 | map( 73 | preceded( 74 | tuple(( 75 | tag("set"), 76 | opt(Separator::parse), 77 | opt(terminated(CppTypeRef::parse, opt(Separator::parse))), 78 | )), 79 | delimited( 80 | pair(cchar('<'), opt(Separator::parse)), 81 | FieldTypeRef::parse, 82 | pair(opt(Separator::parse), cchar('>')), 83 | ), 84 | ), 85 | |v| Self::Set(Box::new(v)), 86 | ), 87 | map( 88 | delimited( 89 | pair(tag("list"), opt(Separator::parse)), 90 | delimited( 91 | pair(cchar('<'), opt(Separator::parse)), 92 | FieldTypeRef::parse, 93 | pair(opt(Separator::parse), cchar('>')), 94 | ), 95 | opt(pair(opt(Separator::parse), CppTypeRef::parse)), 96 | ), 97 | |v| Self::List(Box::new(v)), 98 | ), 99 | map(IdentifierRef::parse, Self::Identifier), 100 | ))(input) 101 | } 102 | 103 | pub fn parse_identifier_type(input: &'a str) -> IResult<&'a str, Self> { 104 | map(IdentifierRef::parse, Self::Identifier)(input) 105 | } 106 | } 107 | 108 | impl<'a> Parser<'a> for FieldTypeRef<'a> { 109 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 110 | alt(( 111 | Self::parse_base_type, 112 | Self::parse_container_type, 113 | Self::parse_identifier_type, 114 | ))(input) 115 | } 116 | } 117 | 118 | #[derive(Debug, Clone, PartialEq)] 119 | pub enum FieldType { 120 | Identifier(Identifier), 121 | Bool, 122 | Byte, 123 | I8, 124 | I16, 125 | I32, 126 | I64, 127 | Double, 128 | String, 129 | Binary, 130 | Map(Box, Box), 131 | Set(Box), 132 | List(Box), 133 | } 134 | 135 | impl<'a> From> for FieldType { 136 | fn from(r: FieldTypeRef<'a>) -> Self { 137 | match r { 138 | FieldTypeRef::Identifier(i) => FieldType::Identifier(i.into()), 139 | FieldTypeRef::Bool => FieldType::Bool, 140 | FieldTypeRef::Byte => FieldType::Byte, 141 | FieldTypeRef::I8 => FieldType::I8, 142 | FieldTypeRef::I16 => FieldType::I16, 143 | FieldTypeRef::I32 => FieldType::I32, 144 | FieldTypeRef::I64 => FieldType::I64, 145 | FieldTypeRef::Double => FieldType::Double, 146 | FieldTypeRef::String => FieldType::String, 147 | FieldTypeRef::Binary => FieldType::Binary, 148 | FieldTypeRef::Map(k, v) => { 149 | FieldType::Map(Box::new(k.as_ref().into()), Box::new(v.as_ref().into())) 150 | } 151 | FieldTypeRef::Set(v) => FieldType::Set(Box::new(v.as_ref().into())), 152 | FieldTypeRef::List(v) => FieldType::List(Box::new(v.as_ref().into())), 153 | } 154 | } 155 | } 156 | 157 | impl<'a> From<&FieldTypeRef<'a>> for FieldType { 158 | fn from(r: &FieldTypeRef<'a>) -> Self { 159 | match r { 160 | FieldTypeRef::Identifier(i) => FieldType::Identifier(i.clone().into()), 161 | FieldTypeRef::Bool => FieldType::Bool, 162 | FieldTypeRef::Byte => FieldType::Byte, 163 | FieldTypeRef::I8 => FieldType::I8, 164 | FieldTypeRef::I16 => FieldType::I16, 165 | FieldTypeRef::I32 => FieldType::I32, 166 | FieldTypeRef::I64 => FieldType::I64, 167 | FieldTypeRef::Double => FieldType::Double, 168 | FieldTypeRef::String => FieldType::String, 169 | FieldTypeRef::Binary => FieldType::Binary, 170 | FieldTypeRef::Map(k, v) => { 171 | FieldType::Map(Box::new(k.as_ref().into()), Box::new(v.as_ref().into())) 172 | } 173 | FieldTypeRef::Set(v) => FieldType::Set(Box::new(v.as_ref().into())), 174 | FieldTypeRef::List(v) => FieldType::List(Box::new(v.as_ref().into())), 175 | } 176 | } 177 | } 178 | 179 | impl<'a> Parser<'a> for FieldType { 180 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 181 | FieldTypeRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 182 | } 183 | } 184 | 185 | // CppType ::= 'cpp_type' Literal 186 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 187 | pub struct CppTypeRef<'a>(LiteralRef<'a>); 188 | 189 | impl<'a> Parser<'a> for CppTypeRef<'a> { 190 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 191 | map( 192 | preceded( 193 | tag("cpp_type"), 194 | preceded(Separator::parse, LiteralRef::parse), 195 | ), 196 | Self, 197 | )(input) 198 | } 199 | } 200 | #[derive(derive_newtype::NewType, Eq, PartialEq, Debug, Clone)] 201 | pub struct CppType(Literal); 202 | 203 | impl<'a> From> for CppType { 204 | fn from(r: CppTypeRef<'a>) -> Self { 205 | Self(r.0.into()) 206 | } 207 | } 208 | 209 | impl<'a> Parser<'a> for CppType { 210 | fn parse(input: &'a str) -> IResult<&'a str, Self> { 211 | CppTypeRef::parse(input).map(|(remains, parsed)| (remains, parsed.into())) 212 | } 213 | } 214 | 215 | #[cfg(test)] 216 | mod test { 217 | use crate::utils::*; 218 | 219 | use super::*; 220 | 221 | #[test] 222 | fn test_cpp_type() { 223 | assert_list_eq_with_f( 224 | vec!["cpp_type \"MINI-LUST\"", "cpp_type 'ihciah'"], 225 | vec![LiteralRef::from("MINI-LUST"), LiteralRef::from("ihciah")], 226 | CppTypeRef::parse, 227 | CppTypeRef, 228 | ); 229 | } 230 | 231 | #[test] 232 | fn test_field_type() { 233 | assert_list_eq_with_f( 234 | vec!["bool", "i16"], 235 | vec![FieldTypeRef::Bool, FieldTypeRef::I16], 236 | FieldTypeRef::parse, 237 | |x| x, 238 | ); 239 | assert_eq!( 240 | FieldTypeRef::parse("map ").unwrap().1, 241 | FieldTypeRef::Map(Box::new(FieldTypeRef::Bool), Box::new(FieldTypeRef::Bool)) 242 | ); 243 | assert_eq!( 244 | FieldTypeRef::parse("map").unwrap().1, 245 | FieldTypeRef::Map(Box::new(FieldTypeRef::Bool), Box::new(FieldTypeRef::Bool)) 246 | ); 247 | assert_eq!( 248 | FieldTypeRef::parse("set ").unwrap().1, 249 | FieldTypeRef::Set(Box::new(FieldTypeRef::Bool)) 250 | ); 251 | assert_eq!( 252 | FieldTypeRef::parse("set").unwrap().1, 253 | FieldTypeRef::Set(Box::new(FieldTypeRef::Bool)) 254 | ); 255 | assert_eq!( 256 | FieldTypeRef::parse("list ").unwrap().1, 257 | FieldTypeRef::List(Box::new(FieldTypeRef::Bool)) 258 | ); 259 | assert_eq!( 260 | FieldTypeRef::parse("list").unwrap().1, 261 | FieldTypeRef::List(Box::new(FieldTypeRef::Bool)) 262 | ); 263 | assert_eq!( 264 | FieldTypeRef::parse("ihc_iah").unwrap().1, 265 | FieldTypeRef::Identifier(IdentifierRef::from("ihc_iah")) 266 | ); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/src/utils.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use nom::IResult; 3 | 4 | #[cfg(test)] 5 | #[allow(unused)] 6 | pub fn assert_pair_eq(input: IResult<&str, T>, expected: T) 7 | where 8 | T: PartialEq + std::fmt::Debug, 9 | { 10 | assert!(input.is_ok()); 11 | assert_eq!(input.unwrap().1, expected); 12 | } 13 | 14 | #[cfg(test)] 15 | #[allow(unused)] 16 | pub fn assert_list_eq<'a, T, IR, IT>(input: IR, expected: IT) 17 | where 18 | IR: IntoIterator>, 19 | IT: IntoIterator, 20 | T: PartialEq + std::fmt::Debug, 21 | { 22 | input 23 | .into_iter() 24 | .zip(expected.into_iter()) 25 | .for_each(|(i, e)| assert_pair_eq(i, e)) 26 | } 27 | 28 | #[cfg(test)] 29 | #[allow(unused)] 30 | pub fn assert_err(input: IResult<&str, T>) 31 | where 32 | T: PartialEq + std::fmt::Debug, 33 | { 34 | assert!(input.is_err()); 35 | } 36 | 37 | #[cfg(test)] 38 | #[allow(unused)] 39 | pub fn assert_list_err<'a, T, IR>(input: IR) 40 | where 41 | IR: IntoIterator>, 42 | T: PartialEq + std::fmt::Debug, 43 | { 44 | input.into_iter().for_each(|i| assert_err(i)); 45 | } 46 | 47 | #[cfg(test)] 48 | #[allow(unused)] 49 | pub fn assert_list_eq_with_f<'a, T, IS, ES, ISI, ESI, IF, EF>( 50 | input: IS, 51 | expected: ES, 52 | input_f: IF, 53 | expected_f: EF, 54 | ) where 55 | T: PartialEq + std::fmt::Debug, 56 | IS: IntoIterator, 57 | ES: IntoIterator, 58 | IF: Fn(ISI) -> IResult<&'a str, T>, 59 | EF: Fn(ESI) -> T, 60 | { 61 | input 62 | .into_iter() 63 | .zip(expected.into_iter()) 64 | .for_each(|(i, e)| assert_pair_eq(input_f(i), expected_f(e))) 65 | } 66 | 67 | #[cfg(test)] 68 | #[allow(unused)] 69 | pub fn assert_list_err_with_f<'a, T, IS, ISI, IF>(input: IS, input_f: IF) 70 | where 71 | T: PartialEq + std::fmt::Debug, 72 | IS: IntoIterator, 73 | IF: Fn(ISI) -> IResult<&'a str, T>, 74 | { 75 | input.into_iter().for_each(|i| assert_err(input_f(i))) 76 | } 77 | -------------------------------------------------------------------------------- /chapter6/thrift-parser/thrift/demo.thrift: -------------------------------------------------------------------------------- 1 | namespace rs demo 2 | 3 | struct A { 4 | 1: required i32 user_id, 5 | 2: required string user_name, 6 | 3: required bool is_male, 7 | 10: optional map extra, 8 | } 9 | 10 | struct User { 11 | 1: required i32 user_id, 12 | 2: required string user_name, 13 | 3: required bool is_male, 14 | 15 | 10: optional map extra, 16 | } 17 | 18 | struct GetUserRequest { 19 | 1: i32 user_id, 20 | 2: string user_name, 21 | 3: bool is_male, 22 | } 23 | 24 | struct GetUserResponse { 25 | 1: required list users, 26 | } 27 | 28 | service ItemService { 29 | GetUserResponse GetUser (1: GetUserRequest req, 2: bool shuffle), 30 | } -------------------------------------------------------------------------------- /chapter7/Chapter7.md: -------------------------------------------------------------------------------- 1 | # 服务发现 2 | 3 | 基于 tower 的服务发现和 LB。 -------------------------------------------------------------------------------- /chapter8/Chapter8.md: -------------------------------------------------------------------------------- 1 | # Middleware 2 | 3 | 添加中间件的支持。 --------------------------------------------------------------------------------