用 Rust 生成 Ant-Design Table Columns
经常开发表格,是不是已经被手写Ant-Design Table的Columns整烦了?
尤其是ToB项目,表格经常动不动就几十列。每次照着后端给的接口文档一个个配置,太头疼了,主要是有时还会粘错就尴尬了。
那有没有办法能自动生成columns配置呢?
当然可以。
目前后端的接口文档一般是使用Swagger来生成的,Swagger是基于OpenAPI规范的一种实现。(OpenAPI规范是一种描述RESTful API的语言无关的格式,它允许开发者定义API的操作、输入和输出参数、错误响应等信息,并提供了一种规范的方式来描述和交互API。)
那么我们只需要解析Swagger的配置就可以反向生成前端代码。
接下来我们就写个CLI工具来生成Table Columns。
平常我们实现一个CLI工具一般都是用Node,今天我们搞点不一样的,用Rust。
开始咯
swagger.json
打开后端用swagger生成的接口文档中的一个接口,一般是下面这样的,可以看到其json配置文件,如下图:

swagger: 2.0表明了这个文档使用的swagger版本,不同版本json配置结构会不同。
paths这里key是接口地址。
可以看到当前接口是“/api/operations/cate/rhythmTableList”。
顺着往下看,“post.responses.200.schema.originalRef”,这就是我们要找的,这个接口对应的返回值定义。
definitions拿到上面的返回值定义,就可以在“definitions”里找到对应的值。
这里是“definitions.ResponseResult«List«CateInsightRhythmListVO»».properties.data.items.originalRef”
通过他就可找到返回的实体类定义CateInsightRhythmListVO
CateInsightRhythmListVO这里就是我们生成Table Columns需要的字段定义了。
CLI
接下来制作命令行工具
起初我使用的是commander-rust,感觉用起来更符合直觉,全程采用macros定义即可。
但到发布的时候才发现,Rust依赖必须有一个确定的版本,commander-rust目前使用的是分支解析。。。
最后还是换了clap
clap的定义就要繁琐些,如下:
#[derive(Parser)]
#[command(author, version)]
#[command(about = "swagger_to - Generate code based on swagger.json")]
struct Cli {
    #[command(subcommand)]
    command: Option,
}
#[derive(Subcommand)]
enum Commands {
    /// Generate table columns for ant-design
    Columns(JSON),
}
#[derive(Args)]
struct JSON {
    /// path/to/swagger.json
    path: Option,
}
这里使用#[command(subcommand)]和#[derive(Subcommand)]来定义columns子命令
使用#[derive(Args)]定义了path参数,用来让用户输入swagger.json的路径
实现columns子命令
columns命令实现的工作主要是下面几步:
- 读取用户输入的swagger.json 
- 解析swager.json 
- 生成ant-design table columns 
- 生成对应Typescript类型定义 
读取用户输入的swagger.json
这里用到了一个crate,serde_json, 他可以将swagger.json转换为对象。
let file = File::open(json).expect("File should open");
let swagger_json: Value = serde_json::from_reader(file).expect("File should be proper JSON");
解析swager.json
有了swagger_json对象,我们就可以按照OpenAPI的结构来解析它。
/// openapi.rs
pub fn parse_openapi(swagger_json: Value) -> Vec {
    let paths = swagger_json["paths"].as_object().unwrap();
    let apis = paths
        .iter()
        .map(|(path, path_value)| {
            let post = path_value["post"].as_object().unwrap();
            let responses = post["responses"].as_object().unwrap();
            let response = responses["200"].as_object().unwrap();
            let schema = response["schema"].as_object().unwrap();
            let original_ref = schema["originalRef"].as_str().unwrap();
            let data = swagger_json["definitions"][original_ref]["properties"]["data"]
                .as_object()
                .unwrap();
            let items = data["items"].as_object().unwrap();
            let original_ref = items["originalRef"].as_str().unwrap();
            let properties = swagger_json["definitions"][original_ref]["properties"]
                .as_object()
                .unwrap();
            let response = properties
                .iter()
                .map(|(key, value)| {
                    let data_type = value["type"].as_str().unwrap();
                    let description = value["description"].as_str().unwrap();
                    ResponseDataItem {
                        key: key.to_string(),
                        data_type: data_type.to_string(),
                        description: description.to_string(),
                    }
                })
                .collect();
            Api {
                path: path.to_string(),
                model_name: original_ref.to_string(),
                response: response,
            }
        })
        .collect();
    return apis;
}
这里我写了一个parse_openapi()方法,用来将swagger.json解析成下面这种形式:
[
  {
    path: 'xxx',
    model_name: 'xxx',
    response: [
      {
        key: '字段key',
        data_type: 'number',
        description: '字段名'
      }
    ]
  }
]
对应的Rust结构定义是这样的:
pub struct ResponseDataItem {
    pub key: String,
    pub data_type: String,
    pub description: String,
}
pub struct Api {
    pub path: String,
    pub model_name: String,
    pub response: Vec<ResponseDataItem>,
}
生成ant-design table columns
有了OpenAPI对象就可以生成Table Column了,这里写了个generate_columns()方法:
/// generator.rs
pub fn generate_columns(apis: &mut Vec) -> String {
    let mut output_text = String::new();
    output_text.push_str("import type { ColumnsType } from 'antd'\n");
    output_text.push_str("import type * as Types from './types'\n");
    output_text.push_str("import * as utils from './utils'\n\n");
    for api in apis {
        let api_name = api.path.split('/').last().unwrap();
        output_text.push_str(
            &format!(
                "export const {}Columns: ColumnsType = [\n",
                api_name,
                api.model_name
            )
        );
        for data_item in api.response.clone() {
            output_text.push_str(
                &format!(
                    "  {{\n    title: '{}',\n    key: '{}',\n    dataIndex: '{}',\n    {}\n  }},\n",
                    data_item.description,
                    data_item.key,
                    data_item.key,
                    get_column_render(data_item.clone())
                )
            );
        }
        output_text.push_str("]\n");
    }
    return output_text;
}
这里主要就是采用字符串模版的形式,将OpenAPI对象遍历生成ts代码。
生成对应Typescript类型定义
Table Columns的类型使用generate_types()来生成,原理和生成columns一样,采用字符串模版:
/// generator.rs
pub fn generate_types(apis: &mut Vec) -> String {
    let mut output_text = String::new();
    for api in apis {
        let api_name = api.path.split('/').last().unwrap();
        output_text.push_str(
            &format!(
                "export type {} = {{\n",
                Some(api.model_name.clone()).unwrap_or(api_name.to_string())
            )
        );
        for data_item in api.response.clone() {
            output_text.push_str(&format!("  {}: {},\n", data_item.key, data_item.data_type));
        }
        output_text.push_str("}\n\n");
    }
    return output_text;
}
main.rs
然后我们在main.rs中分别调用上面这两个方法即可
/// main.rs
let mut apis = parse_openapi(swagger_json);
    let columns = generator::generate_columns(&mut apis);
    let mut columns_ts = File::create("columns.ts").unwrap();
    write!(columns_ts, "{}", columns).expect("Failed to write to output file");
    let types = generator::generate_types(&mut apis);
    let mut types_ts = File::create("types.ts").unwrap();
    write!(types_ts, "{}", types).expect("Failed to write to output file");
对于columns和types分别生成两个文件,columns.ts和types.ts。
!这里有一点需要注意
当时开发的时候对Rust理解不是很深,起初拿到parse_openapi返回的apis我是直接分别传给generate_columns(apis)和generate_types(apis)的。但编译的时候报错了:

这对于js很常见的操作竟然在Rust中报错了。原来Rust所谓不依赖运行时垃圾回收而管理变量分配引用的特点就体现在这里。
我就又回去读了遍Rust教程里的“引用和借用”那篇,算是搞懂了。这里实际上是Rust变量所有权、引用和借用的问题。读完了自然你也懂了。
看看效果
安装
cargo install swagger_to
使用
swagger_to columns path/to/swagger.json
会在swagger.json所在同级目录生成三个文件:
columns.tsant-design table columns的定义
types.tscolumns对应的类型定义
utils.tscolumn中render对number类型的字段添加了格式化工具

Enjoy
作者:京东零售 于弘达
来源:京东云开发者社区
用 Rust 生成 Ant-Design Table Columns的更多相关文章
- Ant Design Table 如何动态自定义?Ant Popover 遮挡?
		项目场景: 基于electron + Vue + node.js + express + mysql + evanpatchouli-mysql + Ant-Design-Vue,编写一款属于自己的轻 ... 
- ant design Table合并单元格合并单元格怎么用?
		1.ant design table合并单元格怎么用? 
- 2017.11.6 - ant design table等组件的使用,以及 chrome 中 network 的使用
		一.今日主要任务 悉尼小程序后台管理开发: 景点管理页面: 获取已有数据列表,选取部分数据呈现在表格中,根据景点名称.分类过滤出对应的景点. 二.难点 1. 项目技术选取: ant design. ... 
- ant design table column 设置width不生效解决方案
		当td里的内容超出了width的范围时,会出现width不固定,也就是width不生效 解决方案: 设置scroll的width等于所有列宽之和(scroll={{x: 100%}}) 
- ant design table td 文字显示过长添加省略号、ant 文字过长时添加tootip提示
		方法1: overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit ... 
- 同时使用 Ant Design of React 中 Mention 和 Form
		使用场景,在一个列表中,点击每一行会弹出一个表单,通过修改表单数据并提交来修改这一行的数据,其中某个数据的填写需要通过Mention实现动态提示及自动补全的功能. 具体效果为: 遇到的问题: 1.希望 ... 
- Ant Design项目记录和CSS3的总结和Es6的基本总结
		这里主要是介绍自己运用ANT框架的一些小总结,以前写到word里,现在要慢慢传上来, 辅助生殖项目总结:从每个组件的运用的方法和问题来总结项目. 1.项目介绍 辅助生殖项目主要运用的是Ant.desi ... 
- 使用ant design pro搭建项目
		脚手架搭建 git clone --depth=1 https://github.com/ant-design/ant-design-pro.git my-project 然后 cd my-proje ... 
- Ant Design 学习记录
		遇到的问题: 点击列表中的一个字段 , 显示出一条指定id(其他筛选条件的)数据 解决这个问题之前,要先了解 Antd的 Table中的 Column 列描述数据对象,是 columns 中的一项 ... 
- Ant Design Pro的dva-loading
		loading为dva的插件,全局可用,它里面维护了一些布尔值,用于控制loading动画效果的显示与隐藏,通过@connect()来注入使用 官网介绍如下: https://dvajs.com/ 在 ... 
随机推荐
- vue中获取所有路由
			在router实例上有options属性: 
- xlsx纯前端导出表格,完善边框等样式
			仅用xlsx是无法实现文字样式及表格边框的style的,因此配合使用xlsx-style 以下源码直接复制过去用 // 源码什么的都不需要改动 import * as XLSXStyle from ' ... 
- Ffmpeg 视频压缩的几个关键参数
			Ffmpeg的视频操作官网文档:https://ffmpeg.org/ffmpeg-filters.html#Video-Filters 视频压缩用到的参数主要为以下几个: 文件路径:-i 输入文件的 ... 
- CF1477F Nezzar and Chocolate Bars 题解
			题意: 有一根长为 \(1\) 的巧克力,已经被切了 \(m-1\) 刀被分成 \(m\) 分,接下来每次在整根长度为 \(1\) 的巧克力上均匀随机一个点切一刀,求每一小段巧克力长度均小于一个给定值 ... 
- 2020-10-28:go中,好几个go程,其中一个go程panic,会产生什么问题?
			福哥答案2020-10-28: 1.运行时恐慌,当panic被抛出异常后,如果我们没有在程序中添加任何保护措施的话,程序就会打印出panic的详细情况之后,终止运行.2.有panic的子协程里的def ... 
- 2021-01-03:java中,描述一下什么情况下,对象会从年轻代进入老年代?
			福哥答案2021-01-03: 1.对象的年龄超过一定阀值,-XX:MaxTenuringThreshold 可以指定该阀值.2.动态对象年龄判定,有的垃圾回收算法,比如 G1,并不要求 age 必须 ... 
- 2021-05-07:给定一个数组arr,你可以在每个数字之前决定+或者-,但是必须所有数字都参与 ,再给定一个数target,请问最后算出target的方法数是多少?
			2021-05-07:给定一个数组arr,你可以在每个数字之前决定+或者-,但是必须所有数字都参与 ,再给定一个数target,请问最后算出target的方法数是多少? 福大大 答案2021-05-0 ... 
- 【GiraKoo】线程本地存储(Thread Local Storage, TLS)
			[技术分享]线程本地存储(Thread Local Storage, TLS) 在项目开发中,遇到了关于TLS相关的问题.为了了解该机制的用途,在微软的官网查找了一些资料. 本文参考官方文档, 简单介 ... 
- 2023-05-20:go语言的slice和rust语言的Vec的扩容流程是什么?
			2023-05-20:go语言的slice和rust语言的Vec的扩容流程是什么? 答案2023-05-20: go语言的slice扩容流程 go版本是1.20.4. 扩容流程见源码见runtime/ ... 
- WPF入门教程系列二十四——DataGrid使用示例(2)
			WPF入门教程系列目录 WPF入门教程系列二--Application介绍 WPF入门教程系列三--Application介绍(续) WPF入门教程系列四--Dispatcher介绍 WPF入门教程系 ... 
