9个版本

0.6.1 2024年8月4日
0.6.0 2024年8月4日
0.5.3 2024年8月3日
0.4.2 2024年7月31日

#1984过程宏

Download history 624/week @ 2024-07-29 105/week @ 2024-08-05

每月下载量 729

MIT 许可协议

24KB
577

gluer

crates.io crates.io docs.rs

一个用于消除前端和后端之间冗余类型和函数定义的Rust框架包装器。目前,它仅支持 axum 框架。

命名来源

"gluer" 这个名字灵感来源于工具的主要功能,即 将基于Rust的Web应用程序的不同部分粘合在一起。正如胶水将不同的材料粘合在一起形成一个整体一样,gluer 将前端和后端的各个组件集成在一起,确保它们无缝工作且没有冗余代码。

安装

将以下内容添加到您的 Cargo.toml

[dependencies]
gluer = "0.7.2"

功能

  • 根据 使用说明 中概述的定义路由和API生成。
  • 所有操作都在宏展开(编译时间)上完成,甚至包括生成TypeScript文件。
  • 完全支持 axum 的类型。
  • 使用以下内容生成TypeScript文件
    • 自定义基本URL
    • 访问API的函数,推断输入和输出类型
    • 结构体作为接口,支持通过 #[meta(...)] 属性更改生成的类型
    • 枚举作为TypeScript的等效类型,不支持有值的枚举,因为TypeScript中缺少该功能
    • 类型作为TypeScript的等效类型
    • 支持将文档字符串转换为TypeScript的等效类型,包括结构体和枚举的字段
    • 元组作为TypeScript的等效类型,也支持在 axum 的路径中元组
    • 支持将Rust特定类型Result转换为自定义类型,使用custom = [Type, *]属性
    • 泛型,包括多个和嵌套的泛型,可以在这里找到示例
    • 无需额外依赖

如何使用

gluer会生成一个API端点.ts文件。要使用它,请按照以下步骤操作

步骤 1: 定义结构体和函数

要定义你的结构体、函数和枚举,请使用#[metadata]宏以及#[meta(...)] 属性。这将启用generate!宏找到这些内容并将它们转换为TypeScript等效类型。

use axum::{
    Json,
    extract::Path,
};
use gluer::metadata;
use serde::{Serialize, Deserialize};

/// Define a struct with the metadata macro
/// Note: This is a docstring and will be
/// converted to the TypeScript equivalent
#[metadata(custom = [Result])]
#[derive(Serialize, Deserialize)]
struct Book {
    /// This will also be converted to a docstring
    name: String,
    // When you use types as `Result`, `Option` or `Vec` the 
    // macro sees them as a default rust type, meaning when
    // you wanting to use custom ones you have to specify that
    // via the `custom` attribute on `#[metadata]`
    some_result: Result<String>,
    // Sometimes you don't have access to certain data types, 
    // so you can override them using `#[meta(into = Type)]` 
    // or skip them entirely via `#[meta(skip)]`
    #[meta(into = String)]
    user: User,
    #[meta(skip)]
    borrower: User,
}

// Everything you want to use, even if it's just a
// dependency of struct or type, needs to be declared
// with the `#[metadata]` macro
#[metadata]
type Result<T> = std::result::Result<T, String>;

#[derive(Default, Serialize, Deserialize)]
struct User {
    name: String,
    password: String,
}

// Define an enum with the metadata macro
// Note: Enums with values are not supported
#[metadata]
#[derive(Default, Serialize, Deserialize)]
enum BookState {
    #[default]
    None,
    Reserved,
    Borrowed,
}

// Define the functions with the metadata macro
#[metadata]
async fn root() -> Json<String> {
    "Hello, World!".to_string().into()
}

// Supports axum's input types
#[metadata]
async fn book(Json(b): Json<Book>) -> Json<Book> {
    Json(b)
}

// Also tuples in paths
#[metadata]
async fn path(Path(p): Path<(String, String)>) -> Json<(String, String)> {
    p.into()
}

// Supports enums 
#[metadata]
async fn book_state() -> Json<BookState> {
    BookState::default().into()
}

步骤 2: 添加路由

使用route!宏结合axum的Router来添加路由。这将启用generate!宏识别路由并生成相应的函数、结构体、类型和枚举。注意,内联函数不能使用,因为生成的TypeScript文件中的函数名是从处理程序函数名推断出来的。

use axum::{
    routing::get,
    Json,
    Router,
    extract::Path,
};
use gluer::{route, metadata};

// without `#[metadata]`, it's non-API-important
async fn root() -> String {
    "Hello, World!".to_string()
}

// done like above
#[metadata]
async fn hello(Path(h): Path<String>) -> Json<String> {
    h.into()
}

let mut app: Router<()> = Router::new()
    // Add non-API-important directly on the router
    .route("/", get(root));
// Add API-important routes with the route macro
route!(app, "/:hello", get(hello));
    

步骤 3: 生成API

使用generate!宏生成API文件。此宏在宏展开(编译时间)期间生成TypeScript文件。您需要指定当前项目的project_paths,可以是根目录(表示为"src"),多个目录或特定文件(例如,["dir0", "dir1", "dir2/some.rs"])。将扫描project_paths以检索项目数据,即收集由route!#[metadata]宏标记的信息。此外,您还需要提供一个将生成的文件保存到的path,包括文件名,以及API的base URL。基本URL不应以斜杠结束(/);如果您正在使用axum的静态文件服务,请使用""表示没有基本URL,或者提供类似"https://127.0.0.1:8080"的URL以用于本地服务器。

use gluer::generate;

// Make sure to change "tests" to "src" when copying this example into a normal project
generate!("tests", "tests/api.ts", "");

现在您只需简单地使用路由器启动服务器或进行其他操作,API应由您的LSP生成!

完整示例

以下是使用glueraxum的完整示例

use axum::{
    extract::{Path, Query},
    routing::get,
    Json, Router,
};
use gluer::{generate, metadata, route};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// An example of a simple function with a `Path` and `Query` extractor
#[metadata]
async fn fetch_root(Query(test): Query<HashMap<String, String>>, Path(p): Path<usize>) -> String {
    test.get(&p.to_string()).unwrap().clone()
}

// Generics are supported, multiple even
// Note: This is not a docstring and won't
// be converted
#[metadata]
#[derive(Serialize, Deserialize, Default)]
pub struct Hello<T: Serialize, S> {
    name: S,
    vec: Vec<T>,
}

/// Might want to look into the `api.ts` file to see the docstring for this struct
#[metadata]
#[derive(Serialize, Deserialize, Default)]
struct Age {
    /// Even supports docstring on fields
    age: AgeInner,
}

#[metadata]
#[derive(Serialize, Deserialize, Default)]
struct AgeInner {
    age: u8,
}

#[metadata]
#[derive(Serialize, Deserialize, Default)]
struct Huh<T> {
    huh: T,
}

// Even deep nested generics are supported and tagging default rust types as Custom
#[metadata(custom = [Result])]
async fn add_root(
    Path(_): Path<usize>,
    Json(hello): Json<Result<Hello<Hello<Huh<Huh<Hello<Age, String>>>, String>, String>>>,
) -> Json<Result<String>> {
    Json(Ok(hello.unwrap().name.to_string()))
}

#[metadata]
#[derive(Serialize, Deserialize)]
enum Alphabet {
    A,
    B,
    C,
    // etc
}

// Even tuples are supported
#[metadata]
async fn get_alphabet(Path(r): Path<(Alphabet, S)>) -> Json<(Alphabet, S)> {
    Json(r)
}

/// An example how an api error type could look like
#[metadata]
#[derive(Serialize, Deserialize, Debug)]
enum Error {
    /// Normal 404 error
    NotFound,
    /// Internally something really bad happened
    InternalServerError,
}

// And types?!?
#[metadata]
type Result<T> = std::result::Result<T, Error>;

#[metadata]
type S = String;

#[tokio::main]
async fn main() {
    let mut _app: Router = Router::new();

    route!(_app, "/:p", get(fetch_root).post(add_root));
    route!(_app, "/char/:path/metadata/:path", get(get_alphabet));

    // Make sure to change "tests" to "src" when copying this example into a normal project
    generate!("tests", "tests/api.ts", "");

    let _listener = tokio::net::TcpListener::bind("127.0.0.1:8080")
        .await
        .unwrap();
    // starts the server, comment in and rename `_listener` to run it
    // axum::serve(listener, app).await.unwrap();
}

依赖项

~285–740KB
~18K SLoC