#documentation #openapi #rest-api #web #compile-time #hypers

hypers_openapi

Compile time generated OpenAPI documentation for hypers

9 unstable releases (3 breaking)

new 0.3.3 Jun 4, 2024
0.3.2 Jun 1, 2024
0.3.0 May 25, 2024
0.2.1 May 8, 2024
0.0.0-alpha Nov 25, 2023

#1296 in Web programming

Download history 9/week @ 2024-02-22 8/week @ 2024-02-29 1/week @ 2024-03-07 2/week @ 2024-03-14 22/week @ 2024-03-28 172/week @ 2024-04-04 7/week @ 2024-04-11 140/week @ 2024-04-25 96/week @ 2024-05-02 47/week @ 2024-05-09 9/week @ 2024-05-16 164/week @ 2024-05-23 273/week @ 2024-05-30

583 downloads per month
Used in hypers

Apache-2.0

3.5MB
6.5K SLoC

examples

hypers_rbatis_admin

⚡️ Quick Start

Cargo.toml

[dependencies]
hypers = { version = "0.8", features = ["full","openapi","scalar","rapidoc","redoc"] }
tokio = { version = "=1.37.0", features = ["full"] }
serde = { version = "=1.0.201", 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)
    }
    // Currently, this type of routing is not supported
    /// 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())
    }
    // Currently, this type of routing is not supported
    #[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(INDEX_HTML)
}

#[tokio::main]
async fn main() -> Result<()> {
    let mut root = Router::default();
    root.get("/", index);
    root.push(Api);

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

    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
}

static INDEX_HTML: &str = 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>
"#;

Dependencies

~6–17MB
~208K SLoC