ohkami

ohkami - [狼] means wolf in Japanese - is simple and macro free web framework for Rust.


Features


Quick start

  1. Add dependencies:

toml [dependencies] ohkami = "0.4"

  1. Write your first code with ohkami:

```rust use ohkami::prelude::*;

fn main() -> Result<()> { Server::setup() .GET("/", || async { Response::OK("Hello, world!") }) .serve_on(":3000") } ```

  1. If you're interested in ohkami, learn more by examples and documentation !


0.3 → 0.4

Added experimental support for middlewares:

```rust fn main() -> Result<()> { let config = Config { logsubscribe: Some( tracingsubscriber::fmt() .withmaxlevel(tracing::Level::TRACE) ), ..Default::default() };

let middleware = Middleware::init()
    .ANY("/*", || async {
        tracing::info!("Hello, middleware!")
    });

let thirdparty_middleware = some_crate::x;

Server::setup_with(config.and(middleware).and(x))
    .GET("/", || async {
        Response::OK("Hello!")
    })
    .serve_on("localhost:3000")

} `` - "Middleware function" is just a function that takes one of nothing,&Contextor&mut Contextas its argument and returns(). You can register such functions with their routes ( where they should work ) in aMiddleware. - Middleware funcs is **inserted before** handler works, so middleware funcs are executed only when **the handler exists** ( e.g. In the code above for example,tracing::info("Hello, middleware")!will NOT executed for a requestPOST /` because no handler for this is found ). - Current design may be changed in future version.


Snippets

handle query params

rust let name = ctx.req.query::<&str>("name")?; // `::<&str>` isn't needed when it's presumable rust let count = ctx.req..query::<usize>("count")?; // `::<usize>` isn't needed when it's presumable

handle request body

rust let body = ctx.req.body::<D>()?; // `::<D>` isn't needed when it's presumable // `D` has to be `serde::Deserialize`

handle path params

```rust fn main() -> Result<()> { Server::setup() .GET("/sleepy/:time/:name", sleepyhello) .serveon(":3000") }

async fn sleepyhello(time: u64, name: String) -> Result { (time < 30) .else(|| Response::BadRequest("sleeping time (sec) must be less than 30."))?; std::thread::sleep( std::time::Duration::from_secs(time) ); Response::OK(format!("Hello {name}, I'm extremely sleepy...")) } ```

return OK response with text/plain

rust Response::OK("Hello, world!") // without Context rust ctx.text("Hello, world!") // with Context

return OK response with application/json

rust Response::OK(JSON("Hello, world!")) // or ctx.json(JSON("Hello, world!")) rust Response::OK(json!("ok": true)) // or ctx.json(json!("ok": true)) rust Response::OK(json(user)?) //or ctx.json(json(user)?) // serialize Rust value into JSON // value's type has to be `serde::Serialize`

handle errors

```rust let user = ctx.req.body::()?;

// or, you can add an error context message: let user = ctx.req.body::() .else(|e| e.errorcontext("failed to get user data"))?;

// or discard original error: let user = ctx.req.body::() .else(|| Response::InternalServerError("can't get user"))?; // or .else(|| Response::InternalServerError(None))?; ```

handle Option values

rust let handler = self.handler.as_ref() ._else(|| Response::NotFound("handler not found"))?; // or ._else(|| Response::NotFound(None))?;

assert boolean conditions

rust (count < 10) ._else(|| Response::BadRequest("`count` must be less than 10"))?; // or ._else(|| Response::BadRequest(None))?;

log config

rust fn main() -> Result<()> { let config = Config { log_subscribe: Some( tracing_subscriber::fmt() .with_max_level(tracing::Level::TRACE) ), ..Default::default() }; Server::setup_with(config) .GET("/", || async {Response::OK("Hello!")}) }

DB config

rust let config = Config { db_profile: DBprofile { pool_options: PgPoolOptions::new().max_connections(20), url: DB_URL.as_str(), }, ..Default::default() };

use sqlx

rust let user = sqlx::query_as::<_, User>( "SELECT id, name FROM users WHERE id = $1" ).bind(1) .fetch_one(ctx.pool()) .await?; // `Response` implements `From<sqlx::Error>`

use middlewares

```rust fn main() -> Result<()> { let middleware = Middleware::init() .ANY("/*", || async { tracing::info!("Hello, middleware!") });

Server::setup_with(middleware)
    .GET("/", || async {
        Response::OK("Hello!")
    })
    .serve_on("localhost:3000")

} rust fn main() -> Result<()> { let config = Config { logsubscribe: Some( tracingsubscriber::fmt() .withmaxlevel(tracing::Level::TRACE) ), ..Default::default() };

let middleware = Middleware::init()
    .ANY("/*", || async {
        tracing::info!("Hello, middleware!")
    });

let thirdparty_middleware = some_external_crate::x;

Server::setup_with(config.and(middleware).and(x))
    .GET("/", || async {
        Response::OK("Hello!")
    })
    .serve_on("localhost:3000")

} ```

test

  1. split setup process from main function: ```rust fn server() -> Server { Server::setup() .GET("/", || async {Response::OK("Hello!")}) }

fn main() -> Result<()> { server().serve_on(":3000") } 2. import `test::Test` and others, and write tests using `assert_to_res` , `assert_not_to_res`: rust

[cfg(test)]

mod test { use ohkami::{server::Server, response::Response, test::{Test, Request, Method}}; use once_cell::sync::Lazy;

static SERVER: Lazy<Server> = Lazy::new(|| super::server());

#[test]
fn test_hello() {
    let request = Request::new(Method::GET, "/");
    (*SERVER).assert_to_res(&request, Response::OK("Hello!"));
    (*SERVER).assert_not_to_res(&request, Response::BadRequest(None));
}

} ```


Development

ohkami is on early stage now and not for producntion use. Please give me your feedback! → GetHub issue


License

This project is under MIT LICENSE (LICENSE-MIT or https://opensource.org/licenses/MIT).