Rust插件库paperclip-core的使用:高效构建Rust Web API的代码生成与OpenAPI集成工具

Rust插件库paperclip-core的使用:高效构建Rust Web API的代码生成与OpenAPI集成工具

安装

在项目目录中运行以下Cargo命令:

cargo add paperclip-core

或者在Cargo.toml中添加以下行:

paperclip-core = "0.7.3"

示例代码

以下是一个使用paperclip-core构建Web API的完整示例:

use paperclip::actix::{
    api_v2_operation,
    // 使用OpenAPI v2规范
    Apiv2Schema,
    // 用于将结构体转为OpenAPI schema
};
use actix_web::{web, App, HttpServer, Responder};

// 定义一个用户结构体,并实现OpenAPI schema
#[derive(Apiv2Schema)]
struct User {
    id: i32,
    name: String,
    email: String,
}

// 定义一个API操作
#[api_v2_operation]
async fn get_users() -> impl Responder {
    let users = vec![
        User {
            id: 1,
            name: "Alice".to_string(),
            email: "alice@example.com".to_string(),
        },
        User {
            id: 2,
            name: "Bob".to_string(),
            email: "bob@example.com".to_string(),
        },
    ];
    web::Json(users)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            // 启用OpenAPI支持
            .wrap(paperclip::actix::OpenApiWrapper::new())
            // 注册API路由
            .service(
                web::resource("/users")
                    // 附加OpenAPI文档
                    .route(web::get().to(get_users)),
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

完整示例

以下是一个更完整的示例,包含创建、查询、更新和删除用户的API:

use paperclip::actix::{
    api_v2_operation, 
    Apiv2Schema,
    OpenApiWrapper
};
use actix_web::{
    web, 
    App, 
    HttpServer, 
    Responder, 
    HttpResponse,
    delete, get, post, put
};
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use std::sync::Mutex;

// 用户数据结构
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
struct User {
    id: i32,
    username: String,
    email: String,
}

// 应用状态
struct AppState {
    users: Mutex<HashMap<i32, User>>,
}

// 创建用户请求体
#[derive(Debug, Serialize, Deserialize, Apiv2Schema)]
struct CreateUserRequest {
    username: String,
    email: String,
}

// 创建用户
#[api_v2_operation]
#[post("/users")]
async fn create_user(
    data: web::Data<AppState>,
    user_data: web::Json<CreateUserRequest>,
) -> impl Responder {
    let mut users = data.users.lock().unwrap();
    let id = users.len() as i32 + 1;
    let user = User {
        id,
        username: user_data.username.clone(),
        email: user_data.email.clone(),
    };
    users.insert(id, user.clone());
    HttpResponse::Created().json(user)
}

// 获取所有用户
#[api_v2_operation]
#[get("/users")]
async fn get_all_users(data: web::Data<AppState>) -> impl Responder {
    let users = data.users.lock().unwrap();
    let users_vec: Vec<User> = users.values().cloned().collect();
    HttpResponse::Ok().json(users_vec)
}

// 获取单个用户
#[api_v2_operation]
#[get("/users/{id}")]
async fn get_user(
    data: web::Data<AppState>,
    id: web::Path<i32>,
) -> impl Responder {
    let users = data.users.lock().unwrap();
    match users.get(&id) {
        Some(user) => HttpResponse::Ok().json(user),
        None => HttpResponse::NotFound().body("User not found"),
    }
}

// 更新用户
#[api_v2_operation]
#[put("/users/{id}")]
async fn update_user(
    data: web::Data<AppState>,
    id: web::Path<i32>,
    user_data: web::Json<CreateUserRequest>,
) -> impl Responder {
    let mut users = data.users.lock().unwrap();
    if let Some(user) = users.get_mut(&id) {
        user.username = user_data.username.clone();
        user.email = user_data.email.clone();
        HttpResponse::Ok().json(user.clone())
    } else {
        HttpResponse::NotFound().body("User not found")
    }
}

// 删除用户
#[api_v2_operation]
#[delete("/users/{id}")]
async fn delete_user(
    data: web::Data<AppState>,
    id: web::Path<i32>,
) -> impl Responder {
    let mut users = data.users.lock().unwrap();
    if users.remove(&id).is_some() {
        HttpResponse::Ok().json("User deleted")
    } else {
        HttpResponse::NotFound().body("User not found")
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // 初始化应用状态
    let app_state = web::Data::new(AppState {
        users: Mutex::new(HashMap::new()),
    });

    HttpServer::new(move || {
        App::new()
            // 添加应用状态
            .app_data(app_state.clone())
            // 启用OpenAPI支持
            .wrap(OpenApiWrapper::new())
            // 注册路由
            .service(create_user)
            .service(get_all_users)
            .service(get_user)
            .service(update_user)
            .service(delete_user)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

特性

  1. 自动生成OpenAPI文档:通过derive宏自动为Rust结构体生成OpenAPI schema
  2. API操作注解:使用api_v2_operation宏轻松标记API端点
  3. 与Actix-web集成:提供Actix-web的中间件和扩展
  4. 代码生成:支持从OpenAPI规范生成Rust代码

许可证

本库采用MIT或Apache-2.0双许可证


1 回复

paperclip-core: 高效构建Rust Web API的代码生成与OpenAPI集成工具

介绍

paperclip-core是一个强大的Rust插件库,专门用于简化Web API开发流程。它提供了代码生成功能和OpenAPI规范集成,让开发者能够更高效地构建类型安全的RESTful API。

主要特性:

  • 自动生成符合OpenAPI规范的API文档
  • 减少样板代码,提高开发效率
  • 与actix-web框架深度集成
  • 提供类型安全的API端点定义
  • 支持从API定义生成客户端代码

使用方法

1. 添加依赖

首先在Cargo.toml中添加依赖:

[dependencies]
paperclip = { version = "0.7", features = ["actix"] }
actix-web = "4.0"

2. 基本API定义示例

use actix_web::{web, App, HttpServer, Responder};
use paperclip::actix::{
    api_v2_operation,
    web::{Json, Path},
    Apiv2Schema,
};

// 定义Pet数据结构
#[derive(serde::Serialize, serde::Deserialize, Apiv2Schema)]
struct Pet {
    id: u64,
    name: String,
    tag: Option<String>,
}

// 定义获取宠物信息的API端点
#[api_v2_operation]
async fn get_pet_by_id(pet_id: Path<u64>) -> Json<Pet> {
    Json(Pet {
        id: *pet_id,
        name: "Fido".to_string(),
        tag: Some("dog".to_string()),
    })
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(paperclip::actix::OpenApiServer::new())
            .service(
                web::resource("/pets/{pet_id}")
                    .route(web::get().to(get_pet_by_id)),
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

3. 更复杂的API示例

use paperclip::actix::{
    api_v2_operation, api_v2_schema,
    web::{Json, Path, Query},
    Apiv2Schema, Apiv2Header,
};

// 分页参数结构体
#[derive(Debug, serde::Serialize, serde::Deserialize, Apiv2Schema)]
struct Pagination {
    limit: Option<u32>,
    offset: Option<u32>,
}

// 错误响应结构体
#[derive(Debug, serde::Serialize, serde::Deserialize, Apiv2Schema)]
struct ErrorResponse {
    message: String,
    code: u32,
}

// 定义获取宠物列表的API端点,包含更丰富的元数据
#[api_v2_operation(
    responses(
        (status = 200, description = "List of pets", body = Vec<Pet>),
        (status = 400, description = "Invalid parameters", body = ErrorResponse)
    ),
    params(Pagination),
    tags("Pets")
)]
async fn list_pets(pagination: Query<Pagination>) -> Json<Vec<Pet>> {
    let pets = vec![
        Pet {
            id: 1,
            name: "Fido".to_string(),
            tag: Some("dog".to_string()),
        },
        Pet {
            id: 2,
            name: "Whiskers".to_string(),
            tag: Some("cat".to_string()),
        },
    ];
    
    Json(pets)
}

完整示例demo

以下是一个完整的API服务示例,包含多个端点和完整配置:

use actix_web::{web, App, HttpServer, Responder};
use paperclip::actix::{
    api_v2_operation, api_v2_schema,
    web::{Json, Path, Query},
    Apiv2Schema, OpenApiServer,
};

// 宠物数据结构
#[derive(Debug, serde::Serialize, serde::Deserialize, Apiv2Schema)]
struct Pet {
    id: u64,
    name: String,
    tag: Option<String>,
}

// 新宠物请求结构体
#[derive(Debug, serde::Serialize, serde::Deserialize, Apiv2Schema)]
struct NewPet {
    name: String,
    tag: Option<String>,
}

// 错误响应结构体
#[derive(Debug, serde::Serialize, serde::Deserialize, Apiv2Schema)]
struct ErrorResponse {
    message: String,
    code: u32,
}

// 分页参数结构体
#[derive(Debug, serde::Serialize, serde::Deserialize, Apiv2Schema)]
struct Pagination {
    limit: Option<u32>,
    offset: Option<u32>,
}

// 获取宠物列表
#[api_v2_operation(
    responses(
        (status = 200, description = "List of pets", body = Vec<Pet>),
        (status = 400, description = "Invalid parameters", body = ErrorResponse)
    ),
    params(Pagination),
    tags("Pets")
)]
async fn list_pets(pagination: Query<Pagination>) -> Json<Vec<Pet>> {
    let pets = vec![
        Pet {
            id: 1,
            name: "Fido".to_string(),
            tag: Some("dog".to_string()),
        },
        Pet {
            id: 2,
            name: "Whiskers".to_string(),
            tag: Some("cat".to_string()),
        },
    ];
    
    Json(pets)
}

// 获取单个宠物
#[api_v2_operation(
    responses(
        (status = 200, description = "Pet details", body = Pet),
        (status = 404, description = "Pet not found", body = ErrorResponse)
    ),
    tags("Pets")
)]
async fn get_pet(pet_id: Path<u64>) -> Json<Pet> {
    Json(Pet {
        id: *pet_id,
        name: "Fido".to_string(),
        tag: Some("dog".to_string()),
    })
}

// 创建新宠物
#[api_v2_operation(
    responses(
        (status = 201, description = "Pet created", body = Pet),
        (status = 400, description = "Invalid input", body = ErrorResponse)
    ),
    tags("Pets")
)]
async fn create_pet(new_pet: Json<NewPet>) -> Json<Pet> {
    Json(Pet {
        id: 3,
        name: new_pet.name.clone(),
        tag: new_pet.tag.clone(),
    })
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(OpenApiServer::new().path("/api-doc"))
            .service(
                web::scope("/api/v1")
                    .service(
                        web::resource("/pets")
                            .route(web::get().to(list_pets))
                            .route(web::post().to(create_pet))
                    )
                    .service(
                        web::resource("/pets/{id}")
                            .route(web::get().to(get_pet))
                    )
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

最佳实践

  1. 模块化组织API:将不同资源的路由和处理函数组织到不同模块中
  2. 充分利用类型系统:为所有请求和响应定义明确的类型
  3. 详细文档注释:为每个端点添加详细的文档说明
  4. 版本控制:考虑API版本控制策略
  5. 错误处理:定义统一的错误响应格式

注意事项

  • paperclip-core主要与actix-web框架配合使用
  • 对于大型项目,考虑将API定义与实现分离
  • 生成的OpenAPI文档可以导入到Postman等API测试工具中
  • 生产环境中可能需要禁用Swagger UI等开发工具

通过使用paperclip-core,开发者可以显著减少Web API开发中的重复工作,同时确保API文档与实现始终保持同步。

回到顶部