Skip to content

Commit 67554a3

Browse files
authored
v0.21 (#325)
* refactor(ws): extract WebSocket implementation to a crate (#274) * introduce mews * check examples/websocket's behavior & fix on ohkami way * use published version of mews & fix to pass tests * feat: `ws` on `rt_worker` (#275) * first support WebSocket on rt_worker in most simple way * add export `StreamExt` from `ws` on `rt_worker` * fix comment * try `ws::worker::Connection` with subset interface of native one * more conpatible `send` & add `Drop` for `Connection` to clean shutdown * update doc comment of `#[bindings]` attribute * update doc comment of `#[bindings]` attribute * add `IntoResponse for worker::Error`, `ws::Context::upgrade_{durable, durable_with}` * remove useless trait impls * not DEBUG * add split on rt_worker * update CI & fix to pass CI / update ohkami_* versions * fix: ws on rt_worker: wrap `ws` in `Rc` instead of clone `ws` itself (#276) * perf(ws on rt_worker): `SessionMap`: use `swap_remove` & omit redundant `None` check (#278) * perf(ws on rt_worker): `SessionMap`: use `Vec::swap_remove` in `remove` & omit redundant `None` check in `get`, `get_mut` * not DEBUG * enhance: headers set optional value (#279) * @2024-11-22 18:32+9:00 * remove useless impl & update doc * not DEBUG * perf: custom headers: use `Vec<(K, V)>` instead of `FxHashMap<K, V>` (#280) * perf: custom headers: use `Vec<(K, V)>` instead of `FxHashMap<K, V>` * fix doc * fix write_unchecked_to * fix impl clear * rename: set `.custom()` -> `.__()` / get `.custom()` -> `.get()` (#281) * README: mention WebSocket on Durable Object on rt_worker (#282) * chore(CI): add `v*` to `on.push.branches` (#283) * chore: fix warnings (#284) * chore: fix warnings on any features * fix to pass CI * refactor: router (#287) * next router * @2024-11-20 06:06+9:00 * next: conversion * next: base Router methods * fix final's logic & add doc / next: base Router methods * next: base Router methods * next: test & fix * update debugs / next: test & fix * fix assert condition * fix split_next_section and usage * pass * clear error and warnings * refactor: runtime abstraction (#288) * skelton * next: for glommio * rt_glommio doesn't exit after Ready * correctly support graceful shutdown on rt_glommio!!! (some optimization may be possible) * fix: properly align waker pointers by Box * remove wrong cfg compile_error!s * let configured in required feature * feat: support `nio` async runtime (#289) * feat: support nio as async runtime * update docs & Taskfile & test / use mews v0.2 * chore: remove feature flag `testing` && export `testing` module behind `#[cfg(debug_assertions)]` (#290) * refactor: remove select macro of interruption handling (#291) * refactor: handle interrupt without `select!` macro -> clean up deps and features * refactor features flag: `"__rt__", `"__rt_native__"` -> just `"__rt_native"` and def `__rt_native = ["__rt__", ...` * refactor ohkami_lib::time (#292) * refactor ohkami_lib::time * fix benches/imf_fixdate * chore: add README note what it is (#293) * rename: `SetHeaders`: `.__()` -> `.x()` (#294) * docs: update doc comment for current `OHKAMI_*` environment variables (#296) * docs: update doc comment for current `OHKAMI_*` environment variables * improve an expression * fix(native): perform `WaitGroup`'s subtraction in `Drop` impl for panic safety (#297) * refactor(sse): `DataStream`: more intuitive colocation and interface (#298) * refactor(sse): colocation, interface * eliminate double-boxing * chore(examples): refactor `examples/openai` -> `examples/chatgpt` (#299) * feat: openapi (#300) * fix Router.search * rethink testing with openapi * add examples/openapi * fix to run example * fix FromParam generation * fix(Request): query structure & parsing * update example * gitignore openapi.json * not skip_serializing any `required` bool * introduce SchemaList trait for anyOf,allOf,oneOf constructing * 2025-01-02 21:21+9:00 * impl Case * next: handle #[serde(flatten)] * fix Fields::Named case * skelton of derive_schema_for_struct * support doc comment * skelton of derive_schema_for_enum * support skipping variant * support doc comment * fix format * fix refizing schemas into recursively * fix typo * fix(macros): to pass cargo check * fix(openapi): to pass cargo check * fix(ohkami): to pass cargo check * fix(macro): schema call & adding description * update examples/openapi * fix skipping field & suppoet skipping Unnamed * fix #[operation] & update examples/openapi * fix `request::Query::iter` & update examples/openapi * update examples/openapi * fix(README): example/openapi * move examples/{realworld, openapi} to samples/ * fix to pass CI itself * fix(CI): perform `test:samples` in `test` * fix warnings * update README * update README * update README * update doc comments * add samples/readme-openapi && fix ohkami_openapi && update README * fix: samples/test.sh * fix: samples/test.sh * update CI * fix: samples/test.sh * fix: samples/test.sh * chore(deps): worker 0.4 -> 0.5 (#301) * openapi: impl Schema for Cow<str> (#302) * feat: `openapi` on `rt_worker` (#303) * 2025-01-16 09:34+9:00 * rename openapi::{request::Input => Inbound} & accept Security { scheme, scopes } * add openapi::Inbound::None and omit Option<_> * update: bounds in fang, request&response impls, samples/worker-with-openapi * fix router::base: merging condition * update OpenAPI generate interface * update `#[worker]` * 2025-01-20 02:14+9:00 * fix #[worker] * update scripts/workers_openapi.js * fix router merging * fix `OpenAPI.servers`: `&[Server]` -> `Vec<Server>` * update scripts/workers_openapi.js * fix router merging * update router Debug * fix operation searching * fix: IntoResponse::openapi_responses: remove default implementation * fix around IntoResponse * fix WorkerMeta parsing * fix tests * update scripts/workers_openapi.js: inherit stdio * update scripts/workers_openapi.js: remove unused imports * fix #[worker] doc comment * update `#[worker]`: read `routes`, `route` of wrangler.toml to apply to default `servers` * refactor #[worker], script * updated scripts/worker_openapi.js: edit output based on `wrangler whoami`, `wrangler.toml` * update README * fix workers_openapi.js: `app.` -> `this.` in `exit` maybe called before initialization * add `wrangler.toml.sample` & update test * install wrangler in CI * install worker-build in CI * cargo install wasm-pack in CI * update test.sh * fix test.sh use `>>` not `>` * update test.sh * add wasm-pack installation (#304) * openapi: fix `sse::DataStream::openapi_responses` (#305) * fix `gen_openapi_doc` & update `sse::DataStream` for openapi & add samples/streaming updating test.sh * diff not `-q` for debugability * fix samples/worker-with-openapi/openapi.json.sample * fix(samples): make `worker-with-openapi` sample work & add test for builtin BasicAuth fang (#306) * fix samples/worker-with-openapi & add test for builtin BasicAuth fang * fix openapi.json.sample * fix openapi.json.sample & remove pages * remove debug code * fix openapi.json.sample * feat: local fangs (#307) * introduce `Ohkami::on` * update samples * todo: skip compression where parent has `with` (non global) fangs * skelton of local fangs * pass test --lib * fix to compile samples * update docs * feat: pass fangs via `Ohkami::new` (#308) * update docs, examples, samples as goals * alter impl & test; remove `Ohkami::with` * fix docs * add fang-only `Routing` impls for better developer experience * restore `Ohkami::with` * `Memory` to `Context` (#309) * add bench (tuplemap_vs_hashmap) * rewrite all * chore(workspace) (#310) * control `[package]` keys at workspace root Cargo.toml except for `name`, `description`, `documentation` * fix docs * feat: `Request.ip` on rt_worker (#311) * feat: `Request.ip` on rt_worker * fix cfg * chore: fix JSDocs (#312) * fix(openapi): resolve compile error on `#[worker] async fn` (#313) * accept unknown flags without `--` and foward to `additionalOptions` (#314) * openapi: workers_openapi.js: use `npx wrangler` instead of `wrangler` (#315) * openapi: workers_openapi.js: use `npx wrangler` instead of `wrangler` * fix sample * fix(openapi): consider that `process.argv` doesn't have script path in `node -e` (#316) * chore: remove badgaes from lib.rs's inner doc (#317) * chore: update README, crate inner doc (#318) * docs: add doc comment for `Ohkami::{new, with}` (#319) * docs: add doc comment for `Ohkami::{new, with}` * fix doc sample codes * chore: add `Publish` workflow (#320) * chore: check if branch is main in `Publish` workflow (#321) * docs: add `handler`, `path params` section in `Ohkami::new` doc (#322) * docs: `{Name}` -> `"{Name}"` literal-expected args (#323) * docs: add note about optional query params and `Option<Query<T>>` (#324)
1 parent 92e4458 commit 67554a3

File tree

178 files changed

+11051
-4662
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

178 files changed

+11051
-4662
lines changed

Diff for: .github/workflows/CI.yml

+15-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: CI
33
on:
44
pull_request:
55
push:
6-
branches: [main]
6+
branches: [main, v*]
77

88
jobs:
99
CI:
@@ -23,30 +23,37 @@ jobs:
2323
echo 'linker = "clang"' >> $HOME/.cargo/config.toml
2424
echo 'rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]' >> $HOME/.cargo/config.toml
2525
26+
- uses: actions/setup-node@v4
27+
with:
28+
node-version: latest
29+
30+
- run: npm install -g wrangler
31+
2632
- uses: dtolnay/rust-toolchain@master
2733
with:
2834
toolchain: ${{ matrix.toolchain }}
2935
targets: x86_64-unknown-linux-gnu, wasm32-unknown-unknown
3036

31-
- name: Cache cargo subcommands
32-
id: cache_cargo_subcommands
33-
uses: actions/cache@v3
37+
- name: Cache cargo bin
38+
id: cache_cargo_bin
39+
uses: actions/cache@v4
3440
with:
35-
key: ${{ runner.os }}-cargo-install--sqlx-sccache
41+
key: ${{ runner.os }}-cargo-bin
3642
path: ~/.cargo/bin
37-
- name: Install cargo subcommands
38-
if: ${{ steps.cache_cargo_subcommands.outputs.cache-hit != 'true' }}
43+
- name: Install cargo commands
44+
if: ${{ steps.cache_cargo_bin.outputs.cache-hit != 'true' }}
3945
run: |
4046
cargo install sqlx-cli --no-default-features --features native-tls,postgres
4147
cargo install sccache --locked
48+
cargo install wasm-pack worker-build
4249
4350
- name: Setup sccache
4451
run: |
4552
echo '[build]' >> $HOME/.cargo/config.toml
4653
echo "rustc-wrapper = \"$HOME/.cargo/bin/sccache\"" >> $HOME/.cargo/config.toml
4754
- name: Cache sccahe dir
4855
id: cahce_sccahe_dir
49-
uses: actions/cache@v3
56+
uses: actions/cache@v4
5057
with:
5158
key: ${{ runner.os }}-sccahe-dir
5259
path: ~/.cache/sccache

Diff for: .github/workflows/publish.yml

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Publish
2+
3+
on:
4+
push:
5+
tags: ['v*']
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
publish:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
with:
17+
ref: main
18+
fetch-depth: 0
19+
20+
- run: |
21+
BRANCHS=$(git branch --contains ${{ github.ref_name }})
22+
set -- $BRANCHS
23+
for BRANCH in $BRANCHS ; do
24+
if [[ "$BRANCH" == "main" ]]; then
25+
exit 0
26+
fi
27+
done
28+
exit 1
29+
30+
- name: Create release
31+
env:
32+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33+
run: |
34+
gh release create ${{ github.ref_name }} --generate-notes
35+
36+
- name: Publish packages
37+
env:
38+
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
39+
run: |
40+
cargo publish -p ohkami_openapi
41+
cargo publish -p ohkami_macros
42+
cargo publish -p ohkami_lib
43+
cargo publish -p ohkami

Diff for: Cargo.toml

+17-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,30 @@ members = [
44
"ohkami",
55
"ohkami_lib",
66
"ohkami_macros",
7+
"ohkami_openapi",
78
]
89
exclude = [
10+
"samples",
911
"benches",
1012
"benches_rt/glommio",
13+
"benches_rt/nio",
1114
"benches_rt/smol",
1215
"benches_rt/tokio",
1316
"benches_rt/vs_actix-web",
1417
]
1518

19+
[workspace.package]
20+
version = "0.21.0"
21+
edition = "2021"
22+
authors = ["kanarus <[email protected]>"]
23+
homepage = "https://crates.io/crates/ohkami"
24+
repository = "https://github.com/ohkami-rs/ohkami"
25+
readme = "README.md"
26+
keywords = ["async", "http", "web", "server", "framework"]
27+
categories = ["asynchronous", "web-programming::http-server", "network-programming", "wasm"]
28+
license = "MIT"
29+
1630
[workspace.dependencies]
17-
byte_reader = { version = "3.1", features = ["text"] }
18-
serde = { version = "1.0", features = ["derive"] }
31+
byte_reader = { version = "3.1", features = ["text"] }
32+
serde = { version = "1.0", features = ["derive"] }
33+
serde_json = { version = "1.0" }

Diff for: README.md

+123-26
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,24 @@
66
<br>
77

88
- *macro-less and type-safe* APIs for intuitive and declarative code
9-
- *multiple runtimes* are supported:`tokio`, `async-std`, `smol`, `glommio`, `worker` (Cloudflare Workers)
9+
- *various runtimes* are supported:`tokio`, `async-std`, `smol`, `nio`, `glommio` and `worker` (Cloudflare Workers)
10+
- *extremely fast*[Web Frameworks Benchmark](https://web-frameworks-benchmark.netlify.app/result)
11+
- no-network testing, well-structured middlewares, Server-Sent Events, WebSocket, OpenAPI document genration, ...
1012

1113
<div align="right">
1214
<a href="https://github.com/ohkami-rs/ohkami/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/crates/l/ohkami.svg" /></a>
1315
<a href="https://github.com/ohkami-rs/ohkami/actions"><img alt="build check status of ohkami" src="https://github.com/ohkami-rs/ohkami/actions/workflows/CI.yml/badge.svg"/></a>
1416
<a href="https://crates.io/crates/ohkami"><img alt="crates.io" src="https://img.shields.io/crates/v/ohkami" /></a>
1517
</div>
1618

19+
<!--
20+
1721
<br>
1822
1923
## Benchmark Results
2024
2125
- [Web Frameworks Benchmark](https://web-frameworks-benchmark.netlify.app/result)
2226
23-
<!--
24-
2527
- [TechEmpower's Benchmark](https://www.techempower.com/benchmarks)
2628
2729
-->
@@ -34,7 +36,7 @@
3436

3537
```toml
3638
[dependencies]
37-
ohkami = { version = "0.20", features = ["rt_tokio"] }
39+
ohkami = { version = "0.21", features = ["rt_tokio"] }
3840
tokio = { version = "1", features = ["full"] }
3941
```
4042

@@ -78,11 +80,12 @@ Hello, your_name!
7880

7981
## Feature flags
8082

81-
### `"rt_tokio"`, `"rt_async-std"`, `"rt_smol"`, `"rt_glommio"`:native async runtime
83+
### `"rt_tokio"`, `"rt_async-std"`, `"rt_smol"`, `"rt_nio"`, `"rt_glommio"`:native async runtime
8284

8385
- [tokio](https://github.com/tokio-rs/tokio)
8486
- [async-std](https://github.com/async-rs/async-std)
8587
- [smol](https://github.com/smol-rs/smol)
88+
- [nio](https://github.com/nurmohammed840/nio)
8689
- [glommio](https://github.com/DataDog/glommio)
8790

8891
### `"rt_worker"`:Cloudflare Workers
@@ -93,7 +96,7 @@ npm create cloudflare ./path/to/project -- --template https://github.com/ohkami-
9396

9497
then your project directory has `wrangler.toml`, `package.json` and a Rust library crate. Local dev by `npm run dev` and deploy by `npm run deploy` !
9598

96-
See README of the [template](https://github.com/ohkami-rs/ohkami-templates/tree/main/worker) for details.
99+
See README of [template](https://github.com/ohkami-rs/ohkami-templates/tree/main/worker) for details.
97100

98101
### `"sse"`:Server-Sent Events
99102

@@ -102,24 +105,25 @@ Use some reverse proxy to do with HTTP/2,3.
102105

103106
```rust,no_run
104107
use ohkami::prelude::*;
105-
use ohkami::typed::DataStream;
106-
use ohkami::util::stream;
107-
use {tokio::time::sleep, std::time::Duration};
108+
use ohkami::sse::DataStream;
109+
use tokio::time::{sleep, Duration};
108110
109-
async fn sse() -> DataStream<String> {
110-
DataStream::from_stream(stream::queue(|mut q| async move {
111+
async fn handler() -> DataStream {
112+
DataStream::new(|mut s| async move {
113+
s.send("starting streaming...");
111114
for i in 1..=5 {
112115
sleep(Duration::from_secs(1)).await;
113-
q.add(format!("Hi, I'm message #{i} !"))
116+
s.send(format!("MESSAGE #{i}"));
114117
}
115-
}))
118+
s.send("streaming finished!");
119+
})
116120
}
117121
118122
#[tokio::main]
119123
async fn main() {
120124
Ohkami::new((
121-
"/sse".GET(sse),
122-
)).howl("localhost:5050").await
125+
"/sse".GET(handler),
126+
)).howl("localhost:3020").await
123127
}
124128
```
125129

@@ -128,16 +132,16 @@ async fn main() {
128132
Ohkami only handles `ws://`.\
129133
Use some reverse proxy to do with `wss://`.
130134

131-
Currently, WebSocket on `rt_worker` is *not* supported.
135+
WebSocket on Durable Object is available on `"rt_worker"`!
132136

133137
```rust,no_run
134138
use ohkami::prelude::*;
135139
use ohkami::ws::{WebSocketContext, WebSocket, Message};
136140
137-
async fn echo_text(c: WebSocketContext<'_>) -> WebSocket {
138-
c.connect(|mut conn| async move {
141+
async fn echo_text(ctx: WebSocketContext<'_>) -> WebSocket {
142+
ctx.upgrade(|mut conn| async move {
139143
while let Ok(Some(Message::Text(text))) = conn.recv().await {
140-
conn.send(Message::Text(text)).await.expect("Failed to send text");
144+
conn.send(text).await.expect("failed to send text");
141145
}
142146
})
143147
}
@@ -150,7 +154,89 @@ async fn main() {
150154
}
151155
```
152156

153-
### `"nightly"`:enable nightly-only functionalities
157+
### `"openapi"`:OpenAPI document generation
158+
159+
Ohkami supports *as consistent as possible* OpenAPI document generation, where most of the consistency between document and behavior is automatically assured by Ohkami's internal work.
160+
161+
Only you have to
162+
163+
- Derive `openapi::Schema` for all your schema structs
164+
- Make your `Ohkami` call `.generate(openapi::OpenAPI { ... })`
165+
166+
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.
167+
168+
Of course, you can flexibly customize schemas ( by hand-implemetation of `Schema` ), descriptions or other parts ( by `#[operation]` attribute and `openapi_*` hooks ).
169+
170+
```rust,ignore
171+
use ohkami::prelude::*;
172+
use ohkami::format::JSON;
173+
use ohkami::typed::status;
174+
use ohkami::openapi;
175+
176+
// Derive `Schema` trait to generate
177+
// the schema of this struct in OpenAPI document.
178+
#[derive(Deserialize, openapi::Schema)]
179+
struct CreateUser<'req> {
180+
name: &'req str,
181+
}
182+
183+
#[derive(Serialize, openapi::Schema)]
184+
// `#[openapi(component)]` to define it as component
185+
// in OpenAPI document.
186+
#[openapi(component)]
187+
struct User {
188+
id: usize,
189+
name: String,
190+
}
191+
192+
async fn create_user(
193+
JSON(CreateUser { name }): JSON<CreateUser<'_>>
194+
) -> status::Created<JSON<User>> {
195+
status::Created(JSON(User {
196+
id: 42,
197+
name: name.to_string()
198+
}))
199+
}
200+
201+
// (optionally) Set operationId, summary,
202+
// or override descriptions by `operation` attribute.
203+
#[openapi::operation({
204+
summary: "...",
205+
200: "List of all users",
206+
})]
207+
/// This doc comment is used for the
208+
/// `description` field of OpenAPI document
209+
async fn list_users() -> JSON<Vec<User>> {
210+
JSON(vec![])
211+
}
212+
213+
#[tokio::main]
214+
async fn main() {
215+
let o = Ohkami::new((
216+
"/users"
217+
.GET(list_users)
218+
.POST(create_user),
219+
));
220+
221+
// This make your Ohkami spit out `openapi.json`
222+
// ( the file name is configurable by `.generate_to` ).
223+
o.generate(openapi::OpenAPI {
224+
title: "Users Server",
225+
version: "0.1.0",
226+
servers: vec![
227+
openapi::Server::at("localhost:5000"),
228+
]
229+
});
230+
231+
o.howl("localhost:5000").await;
232+
}
233+
```
234+
235+
- Currently, only **JSON** is supported as the document format.
236+
- When the binary size matters, you should prepare a feature flag activating `ohkami/openapi` in your package, and put all your codes around `openapi` behind that feature via `#[cfg(feature = ...)]` or `#[cfg_attr(feature = ...)]`.
237+
- In `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](./scripts/workers_openapi.js) to generate document from `#[ohkami::worker] Ohkami` with `openapi` feature.
238+
239+
### `"nightly"`:nightly-only functionalities
154240

155241
- try response
156242

@@ -162,29 +248,40 @@ async fn main() {
162248

163249
Ohkami's request handling system is called "**fang**s", and middlewares are implemented on this.
164250

165-
*builtin fang* : `CORS`, `JWT`, `BasicAuth`, `Timeout`, `Memory`
251+
*builtin fang* : `CORS`, `JWT`, `BasicAuth`, `Timeout`, `Context`
166252

167253
```rust,no_run
168254
use ohkami::prelude::*;
169255
170256
#[derive(Clone)]
171-
struct GreetingFang;
257+
struct GreetingFang(usize);
172258
173259
/* utility trait; automatically impl `Fang` trait */
174260
impl FangAction for GreetingFang {
175261
async fn fore<'a>(&'a self, req: &'a mut Request) -> Result<(), Response> {
176-
println!("Welcomm request!: {req:?}");
262+
let Self(id) = self;
263+
println!("[{id}] Welcome request!: {req:?}");
177264
Ok(())
178265
}
179266
async fn back<'a>(&'a self, res: &'a mut Response) {
180-
println!("Go, response!: {res:?}");
267+
let Self(id) = self;
268+
println!("[{id}] Go, response!: {res:?}");
181269
}
182270
}
183271
184272
#[tokio::main]
185273
async fn main() {
186-
Ohkami::with(GreetingFang, (
187-
"/".GET(|| async {"Hello, fangs!"})
274+
Ohkami::new((
275+
// register fangs to a Ohkami
276+
GreetingFang(1),
277+
278+
"/hello"
279+
.GET(|| async {"Hello, fangs!"})
280+
.POST((
281+
// register *local fangs* to a handler
282+
GreetingFang(2),
283+
|| async {"I'm `POST /hello`!"}
284+
))
188285
)).howl("localhost:3000").await
189286
}
190287
```

0 commit comments

Comments
 (0)