hypers_openapi

Crates.iohypers_openapi
lib.rshypers_openapi
version0.14.1
sourcesrc
created_at2023-11-25 08:52:21.814024
updated_at2024-09-16 19:33:21.639233
descriptionCompile time generated OpenAPI documentation for hypers
homepage
repository
max_upload_size
id1048142
size3,982,438
(lo-ferris)

documentation

README

examples

hypers_rbatis_admin

⚡️ Quick Start

Cargo.toml

[dependencies]
hypers = { version = "0.14", features = ["full","openapi","debug"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }

Rust Code

use hypers::{hyper::StatusCode, prelude::*, tracing::info};
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
use tokio::sync::Mutex;

static STORE: LazyLock<Db> = LazyLock::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 = "api1 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 todos")]
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())
        }
    }
    #[post("/upload")]
    async fn upload(file: FilePart) -> Response {
        let mut res = Response::default();
        let dest = format!("temp/{}", file.name.clone().unwrap_or("file".to_owned()));
        println!("{dest}");
        let info = if let Err(e) = std::fs::copy(file.path.clone(), std::path::Path::new(&dest)) {
            res.status(StatusCode::INTERNAL_SERVER_ERROR);
            format!("file not found in request: {e}")
        } else {
            format!("File uploaded to {dest}")
        };
        res.render(Text::Plain(info))
    }
    #[post("/uploads")]
    async fn uploads(files: FileParts) -> Response {
        let mut msgs = Vec::with_capacity(files.len());
        let mut res = Response::default();
        for file in files.0 {
            let dest = format!("temp/{}", file.name.clone().unwrap_or("file".to_owned()));
            if let Err(e) = std::fs::copy(file.path.clone(), std::path::Path::new(&dest)) {
                res.status(StatusCode::INTERNAL_SERVER_ERROR)
                    .body(format!("file not found in request: {e}"));
                return res;
            } else {
                msgs.push(dest);
            }
        }
        res.body(format!("Files uploaded:\n\n{}", msgs.join("\n")));
        return res;
    }
}

pub async fn upload(_: Request) -> impl Responder {
    Text::Html(
        r#"<!DOCTYPE html>
            <html>
                <head>
                    <title>Upload file</title>
                </head>
                <body>
                    <h1>Upload file</h1>
                    <form action="/upload" method="post" enctype="multipart/form-data">
                        <input type="file" name="file" />
                        <input type="submit" value="upload" />
                    </form>
                </body>
            </html>
        "#,
    )
}

pub async fn uploads(_: Request) -> Response {
    let res = Response::default();
    res.render(Text::Html(
        r#"<!DOCTYPE html>
            <html>
                <head>
                    <title>Upload files</title>
                </head>
                <body>
                    <h1>Upload files</h1>
                    <form action="/uploads" method="post" enctype="multipart/form-data">
                        <input type="file" name="files" multiple/>
                        <input type="submit" value="upload" />
                    </form>
                </body>
            </html>
        "#,
    ))
}

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>
"#;

#[hook]
pub async fn auth_token(req: Request, next: Next<'_>) -> impl Responder {
    if let Some(session) = req.session() {
        let username = session.get::<String>("username");
        let password = session.get::<String>("password");
        println!("username = {:?}", username);
        println!("password = {:?}", password);
        return next.next(req).await;
    }
    Response::default().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 (Ok(name), Ok(pass)) = (username, password) {
        if name.eq(USER_NAME) && pass.eq(PASS_WORD) {
            let mut session = Session::new();
            let _ = session.insert("username", name);
            let _ = session.insert("password", pass);
            res.set_session(session);
            res.redirect(StatusCode::SEE_OTHER, "/swagger_ui/");
            return res;
        }
    }
    res.render(Text::Html(LOGIN_HTML))
}

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt().init();
    std::fs::create_dir_all("temp").unwrap();

    let openapi = OpenApi::new("todos api", "0.0.1");
    let mut root = OpenApiService::new(openapi)
        .push(Api)
        .push(Base)
        .openapi("/api-doc/openapi.json");

    let session_hook = SessionHook::new(
        CookieStore::new(),
        b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
    )?;
    root.hook(session_hook, None, None);
    root.hook(auth_token, vec!["/swagger_ui/"], vec!["/swaggerLogin"]);

    root.get("/", index); // http://127.0.0.1:7878/
    root.get("/upload", upload);
    root.get("/uploads", uploads);
    root.post("/swaggerLogin", swagger_login);

    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

    info!("router = {:#?}", root);
    let listener = hypers::TcpListener::bind("127.0.0.1:7878").await?;
    hypers::listen(root, listener).await
}
Commit count: 0

cargo fmt