Generate Ant-Design Table Columns with Rust | JD Cloud technical team

Frequently develop tables, have you been annoyed by the Columns of handwritten Ant-Design Table?

Especially for ToB projects, the tables often have dozens of columns at every turn. It is a headache to configure one by one according to the interface documents given by the backend, mainly because sometimes it is embarrassing to stick wrongly.

Is there a way to automatically generate the columns configuration?

sure.

At present, the back-end interface documents are generally generated using Swagger, which is an implementation based on the OpenAPI specification. ( OpenAPIThe specification is a language-independent format for describing RESTful APIs. It allows developers to define API operations, input and output parameters, error responses, and other information, and provides a standardized way to describe and interact with APIs.)

Then we only need to parse the configuration of Swagger to reverse generate the front-end code.

Next we will write a CLI tool to generate Table Columns.

Usually we use Node to implement a CLI tool, but today we do something different, using Rust.

start

swagger.json

Open an interface in the interface document generated by swagger on the back end, which is generally as follows, and you can see its json configuration file, as shown in the following figure:
image

swagger: 2.0Indicates the swagger version used in this document, and the json configuration structure will be different in different versions.

pathsHere key is the interface address.

You can see that the current interface is "/api/operations/cate/rhythmTableList".
Look down, "post.responses.200.schema.originalRef", this is what we are looking for, the return value definition corresponding to this interface.

definitionsGet the above return value definition, you can find the corresponding value in "definitions".
Here is "definitions.ResponseResult «List «CateInsightRhythmListVO»».properties.data.items.originalRef"
through which you can find the returned entity class definitionCateInsightRhythmListVO

CateInsightRhythmListVOHere is the field definition we need to generate Table Columns.

CLI

Next make the command line tool

At first I used commander-rust , and I felt that it was more intuitive to use, and I just used macros to define the whole process.
But it was discovered when it was released that Rust dependencies must have a definite version, and commander-rust currently uses branch resolution. . .
Finally changed the clap

The definition of clap is more complicated, as follows:

#[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,
}


Here use #[command(subcommand)]and #[derive(Subcommand)]to define the columns subcommand
Use #[derive(Args)]defines the path parameter to let the user enter the path of swagger.json

Implement the columns subcommand

The work achieved by the columns command is mainly the following steps:

  1. Read user input swagger.json

  2. Parse swager.json

  3. generate ant-design table columns

  4. Generate the corresponding Typescript type definition

Read user input swagger.json

A crate is used here, serde_jsonwhich can convert swagger.json into an object.

let file = File::open(json).expect("File should open");
let swagger_json: Value = serde_json::from_reader(file).expect("File should be proper JSON");


Parse swager.json

With the swagger_json object, we can parse it according to the OpenAPI structure.

/// 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;
}


Here I wrote a parse_openapi()method to parse swagger.json into the following form:

[
  {
    path: 'xxx',
    model_name: 'xxx',
    response: [
      {
        key: '字段key',
        data_type: 'number',
        description: '字段名'
      }
    ]
  }
]


The corresponding Rust struct definition looks like this:

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>,
}


generate ant-design table columns

With the OpenAPI object, Table Column can be generated. Here is a generate_columns()method:

/// 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;
}


The main thing here is to traverse the OpenAPI object to generate ts code in the form of a string template.

Generate the corresponding Typescript type definition

The type of Table Columns is generate_types()generated by using the same principle as generating columns, using a string template:

/// 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

Then we call the above two methods separately in 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");


Two files are generated for columns and types, columns.ts and types.ts.

! There is one thing to note here

At the time of development, I didn't have a deep understanding of Rust. At first, I got the apis returned by parse_openapi and passed them directly to generate_columns(apis) and generate_types(apis). But an error was reported when compiling:

image

This is a very common operation for js, but an error is reported in Rust. It turns out that Rust's so-called feature of managing variable allocation references without relying on runtime garbage collection is reflected here.
I went back and read the "Reference and Borrowing" article in the Rust tutorial again , and I understood it. This is really a matter of Rust variable ownership, references and borrowing. You will understand after reading it.

look at the effect

Install

cargo install swagger_to


use

swagger_to columns path/to/swagger.json


Three files will be generated in the same directory as swagger.json:

columns.tsDefinition of ant-design table columns

types.tsThe type definition corresponding to columns

utils.tsRender in column adds formatting tools to fields of type number

image

Enjoy

Author: JD Retail Yu Hongda

Source: JD Cloud Developer Community

Musk announced that Twitter will change its name to X and replace the Logo . React core developer Dan Abramov announced his resignation from Meta Clarification about MyBatis-Flex plagiarizing MyBatis-Plus OpenAI officially launched the Android version of ChatGPT ChatGPT for Android will be launched next week, now Started pre-registration Arc browser officially released 1.0, claiming to be a replacement for Chrome Musk "purchased for zero yuan", robbed @x Twitter account VS Code optimized name obfuscation compression, reduced built-in JS by 20%! Bun 0.7, a new high-speed JavaScript runtime , was officially released
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10091205