hypers_openapi 0.12.0

Compile time generated OpenAPI documentation for hypers
Documentation

examples

hypers_rbatis_admin

⚡️ Quick Start

Cargo.toml

[dependencies]
hypers = { version = "0.12", features = ["full","openapi","debug"] }
tokio = { version = "=1.38.0", features = ["full"] }
serde = { version = "=1.0.203", features = ["derive"] }

Rust Code

use hypers::{hyper::StatusCode, once_cell::sync::Lazy, prelude::*};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;

static STORE: Lazy<Db> = Lazy::new(new_store);
pub type Db = Mutex<Vec<Todo>>;

pub fn new_store() -> Db {
    Mutex::new(Vec::new())
}

#[derive(Serialize, Deserialize, Clone, Debug, ToSchema)]
pub struct Todo {
    #[hypers(schema(example = 1))]
    pub id: u64,
    #[hypers(schema(example = "Buy coffee"))]
    pub text: String,
    pub completed: bool,
}

struct Api;
#[openapi(name = "/api", tag = "todos")]
impl Api {
    /// List todos.
    #[get(
        "/list_todos", 
        parameter(
            ("offset", description = "Offset is an query paramter."),
            ("limit", description = "Offset is an query paramter."),
        )
    )]
    async fn list_todos(offset: Query<usize>, limit: Query<usize>) -> Json<Vec<Todo>> {
        let todos = STORE.lock().await;
        let todos: Vec<Todo> = todos
            .clone()
            .into_iter()
            .skip(offset.0)
            .take(limit.0)
            .collect();
        Json(todos)
    }
    /// Create new todo.
    #[post("/create_todo", status(201, 409))]
    async fn create_todo(req: Json<Todo>) -> Result<StatusCode, StatusError> {
        let mut vec = STORE.lock().await;
        for todo in vec.iter() {
            if todo.id == req.id {
                return Err(StatusError::bad_request().detail("todo already exists"));
            }
        }
        vec.push(req.0);
        Ok(StatusCode::CREATED)
    }
}

struct Base;
#[openapi(tag = "api2")]
impl Base {
    /// Update existing todo.
    #[patch("/update_todo/:id", status(200, 404))]
    async fn update_todo(id: Path<u64>, updated: Json<Todo>) -> Result<StatusCode, StatusError> {
        let mut vec = STORE.lock().await;
        for todo in vec.iter_mut() {
            if todo.id == *id {
                *todo = (*updated).clone();
                return Ok(StatusCode::OK);
            }
        }
        Err(StatusError::not_found())
    }
    #[delete("/:id", status(200, 401, 404))]
    async fn delete_todo(id: Path<u64>) -> Result<StatusCode, StatusError> {
        let mut vec = STORE.lock().await;
        let len = vec.len();
        vec.retain(|todo| todo.id != *id);
        let deleted = vec.len() != len;
        if deleted {
            Ok(StatusCode::NO_CONTENT)
        } else {
            Err(StatusError::not_found())
        }
    }
}

pub async fn index(_: Request) -> impl Responder {
    Text::Html(
        r#"<!DOCTYPE html>
            <html>
                <head>
                    <title>Oapi todos</title>
                </head>
                <body>
                    <ul>
                    <li><a href="swagger_ui/" target="_blank">swagger_ui</a></li>
                    <li><a href="scalar" target="_blank">scalar</a></li>
                    <li><a href="rapidoc" target="_blank">rapidoc</a></li>
                    <li><a href="redoc" target="_blank">redoc</a></li>
                    </ul>
                </body>
            </html>
        "#,
    )
}
const USER_NAME: &str = "admin";
const PASS_WORD: &str = "123456";
const LOGIN_HTML: &str = r#"<!DOCTYPE html>
<html>
    <head>
        <title>swagger-ui login</title>
    </head>
    <style>
        html,body{
            margin:0;
            padding:0;
            width:100%;
            height:100%;
        }
        .container{
            display:flex;
            align-item:center;
            justify-content:center;
        }
        .form{
            display:flex;
            align-item:center;
            justify-content:center;
            flex-direction:column;
        }
        .mt-20{
            margin-top: 20px;
        }

    </style>
    <body class="container">
        <form class="form" action="/swaggerLogin" method="post">
            <h1>swagger-ui</h1>
            <input type="text" name="username" placeholder="用户名" />
            <input class="mt-20" type="password" name="password" placeholder="密码" />
            <button class="mt-20" type="submit" id="submit">登录</button>
        </form>
    </body>
</html>
"#;

#[handler]
pub async fn auth_token(req: Request, next: Next<'_>) -> impl Responder {
    let res = Response::default();
    if let Some(_) = req.get::<String>("username") {
        return next.next(req).await;
    } else {
        return res.render(Text::Html(LOGIN_HTML));
    }
}

pub async fn swagger_login(mut req: Request) -> impl Responder {
    let username = req.form::<String>("username").await;
    let password = req.form::<String>("password").await;
    let mut res = Response::default();
    if let (Some(name), Some(pass)) = (username, password) {
        if name.eq(USER_NAME) && pass.eq(PASS_WORD) {
            req.insert("username", name);
            res.redirect(StatusCode::SEE_OTHER, "/swagger_ui/");
            return res;
        }
    }
    res.html(LOGIN_HTML);
    return res;
}

#[tokio::main]
async fn main() -> Result<()> {
    let mut root = Router::default();
    root.get("/", index); // http://127.0.0.1:7878/
    root.hook(auth_token,vec!["/swagger_ui/"], vec!["/swaggerLogin"]);
    root.post("/swaggerLogin", swagger_login);
    
    let openapi = OpenApi::new("todos api", "0.0.1");
    root.openapi("/api-doc/openapi.json", openapi);
    root.push(Api);
    root.push(Base);

    let swagger = SwaggerUi::new("/api-doc/openapi.json");
    root.get("/swagger_ui/*", swagger); // http://127.0.0.1:7878/swagger_ui/

    let rapidoc = RapiDoc::new("/api-doc/openapi.json");
    root.get("/rapidoc", rapidoc); // http://127.0.0.1:7878/rapidoc

    let redoc = ReDoc::new("/api-doc/openapi.json");
    root.get("/redoc", redoc); // http://127.0.0.1:7878/redoc

    let scalar: Scalar = Scalar::new("/api-doc/openapi.json");
    root.get("/scalar", scalar); // http://127.0.0.1:7878/scalar

    println!("root = {:#?}", root);
    let listener = hypers::TcpListener::bind("127.0.0.1:7878").await?;
    hypers::listen(root, listener).await
}