用 Rust 生成 Ant-Design Table Columns

rust,生成,ant,design,table,columns · 浏览次数 : 242

小编点评

代码生成时需要带简单的排版,例如: ``` import type { ColumnsType } from 'antd'; import type * as Types from './types'; import import * as utils from './utils'; // ... for data_item in api.response.clone() { output_text.push_str(&format!( " {}: {},\\", data_item.key, data_item.data_type )); } // ... ``` 这有助于保持代码格式清晰,便于阅读和维护。

正文

经常开发表格,是不是已经被手写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配置文件,如下图:
image

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命令实现的工作主要是下面几步:

  1. 读取用户输入的swagger.json

  2. 解析swager.json

  3. 生成ant-design table columns

  4. 生成对应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)的。但编译的时候报错了:

image

这对于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类型的字段添加了格式化工具

image

Enjoy

作者:京东零售 于弘达

来源:京东云开发者社区

与用 Rust 生成 Ant-Design Table Columns相似的内容:

用 Rust 生成 Ant-Design Table Columns

经常开发表格,是不是已经被手写Ant-Design Table的Columns整烦了?尤其是ToB项目,表格经常动不动就几十列。每次照着后端给的接口文档一个个配置,太头疼了,主要是有时还会粘错就尴尬了。那有没有办法能自动生成columns配置呢?

文盘Rust -- tonic-Rust grpc初体验

gRPC 是开发中常用的开源高性能远程过程调用(RPC)框架,tonic 是基于 HTTP/2 的 gRPC 实现,专注于高性能、互操作性和灵活性。该库的创建是为了对 async/await 提供一流的支持,并充当用 Rust 编写的生产系统的核心构建块。今天我们聊聊通过使用tonic 调用grpc的的具体过程。

中国制霸生成器「GitHub 热点速览 v.22.42」

火遍推特的中国制霸生成器本周一开源就占据了两天的 GitHub Trending 榜,不知道你的足迹遍布了多少个省份呢?同样记录痕迹的 kanal 用了内存读写方式解决了 Rust 的消息处理问题,PHP 应用服务 frankenphp 大概也藏了一手自己的“记录”技能。 除了「记录」主题,RedE

10. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP内网穿透支持修改头信息

用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP内网穿透支持修改头信息项目 涉及HTTP1.1 chunked, http2, keep-alive

8. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP改造篇之HPACK原理

用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP改造篇之HPACK原理 项目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy github: https://github.com/tickbh/wmproxy HTTP/2的

7. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP及TCP内网穿透原理及运行篇

用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP及TCP内网穿透原理及运行篇 项目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy github: https://github.com/tickbh/wmproxy 内网、公

6. 用Rust手把手编写一个wmproxy(代理,内网穿透等), 通讯协议源码解读篇

用Rust手把手编写一个wmproxy(代理,内网穿透等), 通讯协议源码解读篇 项目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy github: https://github.com/tickbh/wmproxy 事件模型的选取 OS线程,

用Rust手把手编写一个Proxy(代理), TLS加密通讯

用Rust手把手编写一个Proxy(代理), TLS加密通讯 项目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy github: https://github.com/tickbh/wmproxy 为什么选择TLS 了解TLS 安全传输层协议(

用Rust手把手编写一个Proxy(代理), 准备篇, 动手造轮子

用Rust手把手编写一个Proxy(代理), 准备篇, 动手造轮子 wmproxy 将实现http/https代理, socks5代理, 后续将实现websocket代理, 内外网穿透等, 会将实现过程分享出来, 希望感兴趣的可以一起参与参与 项目 ++wmproxy++ gite: https:/

用Rust手把手编写一个Proxy(代理), 动工

用Rust手把手编写一个Proxy(代理), 动工 项目 ++wmproxy++ gitee 传送门 github 传送门 设计流程图 flowchart LR A[客户端] -->|Http| B[代理端] --> C[代理服务端] --> D[服务端] B -->|直达| D A -->|Htt