<div align="center">
    <h1>ohkami</h1>
</div>

ohkami *- [狼] wolf in Japanese -* is **declarative** web framework for *nightly* Rust.

## Features
- *macro free, declarative APIs*
- supporting *multi runtime*：`tokio`, `async-std` (and more in future)

<img align="right" alt="build check status of ohkami" src="https://github.com/kana-rus/ohkami/actions/workflows/check.yml/badge.svg"/>
<img align="right" alt="test status of ohkami" src="https://github.com/kana-rus/ohkami/actions/workflows/test.yml/badge.svg"/>

<br/>

## Quick start
1. Add to `dependencies`:

```toml
# this sample uses `tokio` runtime.
# you can choose `async-std` instead.

[dependencies]
ohkami = { version = "0.9.2", features = ["rt_tokio"] }
tokio  = { version = "1",     features = ["full"] }
```
(And ensure your Rust toolchains are **nightly** ones)

2. Write your first code with ohkami：

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

async fn health_check(c: Context) -> Response {
    c.NoContent()
}

async fn hello(c: Context, name: String) -> Response {
    c.OK().text(format!("Hello, {name}!"))
}

#[tokio::main]
async fn main() {
    Ohkami::new()(
        "/hc".
            GET(health_check),
        "/hello/:name".
            GET(hello),
    ).howl(3000).await
}
```

<br/>

## Samples

### handle path/query params
```rust
use ohkami::prelude::*;
use ohkami::utils::Query;


#[tokio::main]
async fn main() {
    Ohkami::new()(
        "/api/users/:id".
            GET(get_user).
            PATCH(update_user),
    ).howl("localhost:5000").await
}

async fn get_user(c: Context,
    id: usize /* <-- path param */
) -> Response {

    // ...

    c.OK().json(found_user)
}


#[Query]
struct UpdateUserQuery {
    q: Option<u64>
}

async fn update_user(c: Context,
    id:    usize,        /* <-- path  param */
    query: UpdateUserQuery, /* <-- query params */
) -> Response {

    // ...

    c.NoContent()
}
```
Use tuple like `(verion, id): (u8, usize),` for multiple path params.

<br/>

### handle request body
```rust
use ohkami::{prelude::*, utils::Payload};


#[Payload(JSON)]
#[derive(serde::Deserialize)]
struct CreateUserRequest {
    name:     String,
    password: String,
}

async fn create_user(c: Context,
    body: CreateUserRequest
) -> Response {

    // ...

    c.NoContent()
}


#[Payload(URLEncoded)]
struct LoginInput {
    name:     String,
    password: String,
}

#[derive(serde::Serialize)]
struct Credential {
    token: String,
}

async fn post_login(c: Context,
    input: LoginInput
) -> Response {

    // ...

    let token = // ...

    c.OK().json(Credential { token })
}
```

<br/>

### use middlewares
ohkami's middlewares are called "**fang**s".
```rust
use ohkami::prelude::*;

#[tokio::main]
async fn main() {
    Ohkami::with((append_server, log_response))(
        "/"  .GET(root),
        "/hc".GET(health_check),
        "/api/users".
            GET(get_users).
            POST(create_user),
    ).howl(":8080").await
}

fn append_server(c: &mut Context, req: Request) -> Request {
    c.headers
        .Server("ohkami");
    req
}

fn log_response(res: Response) -> Response {
    println!("{res:?}");
    res
}
```

<br/>

### pack of Ohkamis
```rust
#[tokio::main]
async fn main() {
    // ...

    let users_ohkami = Ohkami::new()(
        "/".
            POST(create_user),
        "/:id".
            GET(get_user).
            PATCH(update_user).
            DELETE(delete_user),
    );

    Ohkami::new()(
        "/hc"       .GET(health_check),
        "/api/users".By(users_ohkami), // <-- nest by `By`
    ).howl(5000).await
}
```

<br/>

### error handling
Use **`.map_err(|e| c. /* error_method */ )?`** in most cases：

```rust
async fn handler1(c: Context) -> Response {
    make_result()
        .map_err(|e| c.InternalServerError())?;

    // ...
}

async fn handler2(c: Context) -> Response {
    let user = generate_dummy_user()
        .map_err(|e| c.InternalServerError()
            .text("in `generate_dummy_user`"))?;
    
    // ...
}
```

<br/>

## License
`ohkami` is licensed under MIT LICENSE ([LICENSE-MIT](https://github.com/kana-rus/ohkami/blob/main/LICENSE-MIT) or [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT)).
