Crates.io | ohkami_lib |
lib.rs | ohkami_lib |
version | |
source | src |
created_at | 2024-02-09 00:38:47.594974 |
updated_at | 2025-02-14 17:32:21.833363 |
description | internal library for Ohkami - intuitive and declarative web framework |
homepage | https://crates.io/crates/ohkami |
repository | https://github.com/ohkami-rs/ohkami |
max_upload_size | |
id | 1132830 |
Cargo.toml error: | TOML parse error at line 18, column 1 | 18 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
tokio
, async-std
, smol
, nio
, glommio
and worker
(Cloudflare Workers), lambda
(AWS Lambda)dependencies
:[dependencies]
ohkami = { version = "0.23", features = ["rt_tokio"] }
tokio = { version = "1", features = ["full"] }
use ohkami::prelude::*;
use ohkami::typed::status;
async fn health_check() -> status::NoContent {
status::NoContent
}
async fn hello(name: &str) -> String {
format!("Hello, {name}!")
}
#[tokio::main]
async fn main() {
Ohkami::new((
"/healthz"
.GET(health_check),
"/hello/:name"
.GET(hello),
)).howl("localhost:3000").await
}
$ cargo run
$ curl http://localhost:3000/healthz
$ curl http://localhost:3000/hello/your_name
Hello, your_name!
"rt_tokio"
, "rt_async-std"
, "rt_smol"
, "rt_nio"
, "rt_glommio"
: native async runtime"rt_worker"
: Cloudflare WorkersWorks with worker crate.
npm create cloudflare <project dir> -- --template https://github.com/ohkami-rs/ohkami-templates/worker
then <project dir>
will have wrangler.toml
, package.json
and a Rust library crate.
A #[ohkami::worker]
(async/sync) fn returning Ohkami
is the Worker definition.
Local dev by npm run dev
and deploy by npm run deploy
!
See README of template for details.
Or, here are Workers + OpenAPI template and Workers + SPA with Yew template.
"rt_lambda"
: AWS Lambdaexperimental
Function URLs
and API Gateway
are supportedWorks with lambda_runtime crate ( and tokio ).
cargo lambda will be good partner.
Let's :
cargo lambda new <project dir> --template https://github.com/ohkami-rs/ohkami-templates
lambda_runtime::run(your_ohkami)
make you_ohkami
run on Lambda Function.
Local dev by
cargo lambda watch
and deploy by
cargo lambda build --release [--compiler cargo] [and more]
cargo lambda deploy [--role <arn-of-a-iam-role>] [and more]
See
for details.
"sse"
: Server-Sent EventsOhkami responds with HTTP/1.1 Transfer-Encoding: chunked
.
Use some reverse proxy to do with HTTP/2,3.
use ohkami::prelude::*;
use ohkami::sse::DataStream;
use tokio::time::{sleep, Duration};
async fn handler() -> DataStream {
DataStream::new(|mut s| async move {
s.send("starting streaming...");
for i in 1..=5 {
sleep(Duration::from_secs(1)).await;
s.send(format!("MESSAGE #{i}"));
}
s.send("streaming finished!");
})
}
#[tokio::main]
async fn main() {
Ohkami::new((
"/sse".GET(handler),
)).howl("localhost:3020").await
}
"ws"
: WebSocketOhkami only handles ws://
.
Use some reverse proxy to do with wss://
.
use ohkami::prelude::*;
use ohkami::ws::{WebSocketContext, WebSocket, Message};
async fn echo_text(ctx: WebSocketContext<'_>) -> WebSocket {
ctx.upgrade(|mut conn| async move {
while let Ok(Some(Message::Text(text))) = conn.recv().await {
conn.send(text).await.expect("failed to send text");
}
})
}
#[tokio::main]
async fn main() {
Ohkami::new((
"/ws".GET(echo_text),
)).howl("localhost:3030").await
}
"rt_worker"
, both normal ( stateless ) WebSocket and WebSocket on Durable Object are available!"rt_lambda"
, WebSocket is currently not supported."openapi"
: OpenAPI document generation"openapi"
provides highly integrated OpenAPI support.
This enables macro-less, as consistent as possible OpenAPI document generation, where most of the consistency between document and behavior is automatically assured by Ohkami's internal work.
Only you have to
openapi::Schema
for all your schema structsOhkami
call .generate(openapi::OpenAPI { ... })
to generate consistent OpenAPI document.
You don't need to take care of writing accurate methods, paths, parameters, contents, ... for this OpenAPI feature; All they are done by Ohkami.
Of course, you can flexibly customize schemas ( by hand-implemetation of Schema
), descriptions or other parts ( by #[operation]
attribute and openapi_*
hooks ).
use ohkami::prelude::*;
use ohkami::typed::status;
use ohkami::openapi;
// Derive `Schema` trait to generate
// the schema of this struct in OpenAPI document.
#[derive(Deserialize, openapi::Schema)]
struct CreateUser<'req> {
name: &'req str,
}
#[derive(Serialize, openapi::Schema)]
// `#[openapi(component)]` to define it as component
// in OpenAPI document.
#[openapi(component)]
struct User {
id: usize,
name: String,
}
async fn create_user(
JSON(CreateUser { name }): JSON<CreateUser<'_>>
) -> status::Created<JSON<User>> {
status::Created(JSON(User {
id: 42,
name: name.to_string()
}))
}
// (optionally) Set operationId, summary,
// or override descriptions by `operation` attribute.
#[openapi::operation({
summary: "...",
200: "List of all users",
})]
/// This doc comment is used for the
/// `description` field of OpenAPI document
async fn list_users() -> JSON<Vec<User>> {
JSON(vec![])
}
#[tokio::main]
async fn main() {
let o = Ohkami::new((
"/users"
.GET(list_users)
.POST(create_user),
));
// This make your Ohkami spit out `openapi.json`
// ( the file name is configurable by `.generate_to` ).
o.generate(openapi::OpenAPI {
title: "Users Server",
version: "0.1.0",
servers: &[
openapi::Server::at("localhost:5000"),
]
});
o.howl("localhost:5000").await;
}
ohkami/openapi
in your package, and put all your codes around openapi
behind that feature via #[cfg(feature = ...)]
or #[cfg_attr(feature = ...)]
.rt_worker
, .generate
is not available because Ohkami
can't have access to your local filesystem by wasm32
binary on Minifalre. So ohkami provides a CLI tool to generate document from #[ohkami::worker] Ohkami
with openapi
feature."nightly"
: nightly-only functionalitiesbuiltin payload : JSON
, Text
, HTML
, URLEncoded
, Multipart
use ohkami::prelude::*;
use ohkami::typed::status;
/* Deserialize for request */
#[derive(Deserialize)]
struct CreateUserRequest<'req> {
name: &'req str,
password: &'req str,
}
/* Serialize for response */
#[derive(Serialize)]
struct User {
name: String,
}
async fn create_user(
JSON(req): JSON<CreateUserRequest<'_>>
) -> status::Created<JSON<User>> {
status::Created(JSON(
User {
name: String::from(req.name)
}
))
}
use ohkami::prelude::*;
#[tokio::main]
async fn main() {
Ohkami::new((
"/hello/:name/:n"
.GET(hello_n),
"/hello/:name"
.GET(hello),
"/search"
.GET(search),
)).howl("localhost:5000").await
}
async fn hello(name: &str) -> String {
format!("Hello, {name}!")
}
async fn hello_n((name, n): (&str, usize)) -> String {
vec![format!("Hello, {name}!"); n].join(" ")
}
#[derive(Deserialize)]
struct SearchQuery<'q> {
#[serde(rename = "q")]
keyword: &'q str,
lang: &'q str,
}
#[derive(Serialize)]
struct SearchResult {
title: String,
}
async fn search(
Query(query): Query<SearchQuery<'_>>
) -> JSON<Vec<SearchResult>> {
JSON(vec![
SearchResult { title: String::from("ohkami") },
])
}
Ohkami's request handling system is called "fangs", and middlewares are implemented on this.
builtin fang :
Context
( typed interaction with reuqest context )CORS
, JWT
, BasicAuth
Timeout
( native runtime )Enamel
( experimantal; security headers )use ohkami::prelude::*;
#[derive(Clone)]
struct GreetingFang(usize);
/* utility trait; automatically impl `Fang` trait */
impl FangAction for GreetingFang {
async fn fore<'a>(&'a self, req: &'a mut Request) -> Result<(), Response> {
let Self(id) = self;
println!("[{id}] Welcome request!: {req:?}");
Ok(())
}
async fn back<'a>(&'a self, res: &'a mut Response) {
let Self(id) = self;
println!("[{id}] Go, response!: {res:?}");
}
}
#[tokio::main]
async fn main() {
Ohkami::new((
// register fangs to a Ohkami
GreetingFang(1),
"/hello"
.GET(|| async {"Hello, fangs!"})
.POST((
// register *local fangs* to a handler
GreetingFang(2),
|| async {"I'm `POST /hello`!"}
))
)).howl("localhost:3000").await
}
Context
use ohkami::prelude::*;
use ohkami::typed::status;
use sqlx::postgres::{PgPoolOptions, PgPool};
#[tokio::main]
async fn main() {
let pool = PgPoolOptions::new()
.connect("postgres://ohkami:password@localhost:5432/db").await
.expect("failed to connect");
Ohkami::new((
Context::new(pool),
"/users".POST(create_user),
)).howl("localhost:5050").await
}
async fn create_user(
Context(pool): Context<'_, PgPool>,
) -> status::Created {
//...
status::Created(())
}
use ohkami::prelude::*;
#[tokio::main]
async fn main() {
Ohkami::new((
"/".Dir("./dist"),
)).howl("0.0.0.0:3030").await
}
use ohkami::prelude::*;
use ohkami::typed::status;
use ohkami::format::{Multipart, File};
#[derive(Deserialize)]
struct FormData<'req> {
#[serde(rename = "account-name")]
account_name: Option<&'req str>,
pics: Vec<File<'req>>,
}
async fn post_submit(
Multipart(data): Multipart<FormData<'_>>
) -> status::NoContent {
println!("\n\
===== submit =====\n\
[account name] {:?}\n\
[ pictures ] {} files (mime: [{}])\n\
==================",
data.account_name,
data.pics.len(),
data.pics.iter().map(|f| f.mimetype).collect::<Vec<_>>().join(", "),
);
status::NoContent
}
use ohkami::prelude::*;
use ohkami::typed::status;
#[derive(Serialize)]
struct User {
name: String
}
async fn list_users() -> JSON<Vec<User>> {
JSON(vec![
User { name: String::from("actix") },
User { name: String::from("axum") },
User { name: String::from("ohkami") },
])
}
async fn create_user() -> status::Created<JSON<User>> {
status::Created(JSON(User {
name: String::from("ohkami web framework")
}))
}
async fn health_check() -> status::NoContent {
status::NoContent
}
#[tokio::main]
async fn main() {
// ...
let users_ohkami = Ohkami::new((
"/"
.GET(list_users)
.POST(create_user),
));
Ohkami::new((
"/healthz"
.GET(health_check),
"/api/users"
.By(users_ohkami), // nest by `By`
)).howl("localhost:5000").await
}
use ohkami::prelude::*;
use ohkami::testing::*; // <--
fn hello_ohkami() -> Ohkami {
Ohkami::new((
"/hello".GET(|| async {"Hello, world!"}),
))
}
#[cfg(test)]
#[tokio::test]
async fn test_my_ohkami() {
let t = hello_ohkami().test();
let req = TestRequest::GET("/");
let res = t.oneshot(req).await;
assert_eq!(res.status(), Status::NotFound);
let req = TestRequest::GET("/hello");
let res = t.oneshot(req).await;
assert_eq!(res.status(), Status::OK);
assert_eq!(res.text(), Some("Hello, world!"));
}
Latest stable
ohkami is licensed under MIT LICENSE ( LICENSE or https://opensource.org/licenses/MIT ).