From 6ea5c731b3f80d0650976c37a5a36fee741fda3c Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 11:22:31 +0300 Subject: [PATCH 01/10] Feature taxonomy, facade refactor, and API gate Introduce a canonical feature taxonomy (core-*, protocol-*, extras-*) and migrate legacy feature aliases for compatibility. Refactor the rustapi-rs facade to expose stable namespaces (core, protocol, extras) and provide backward-compatible root aliases; update Cargo.toml features accordingly. Add CONTRACT.md to document the public contract and SemVer/MSRV/deprecation policies. Add committed public API snapshots (api/public/*) and a GitHub Actions workflow plus script to detect snapshot drift and require a `breaking` or `feature` PR label. Update templates, CLI, tests, and docs to use the new feature names. Make several internal API adjustments: seal the Handler trait, adjust module visibility/exports in rustapi-core, and update procedural macros to resolve internal crate paths via the facade's __private exports. Remove RELEASES.md and apply related README/architecture documentation tweaks. --- .github/PULL_REQUEST_TEMPLATE.md | 3 +- .github/scripts/public_api_label_gate.sh | 27 + .github/workflows/public-api.yml | 55 ++ CONTRACT.md | 61 +++ README.md | 16 + RELEASES.md | 48 -- api/public/rustapi-rs.all-features.txt | Bin 0 -> 28810 bytes api/public/rustapi-rs.default.txt | Bin 0 -> 15508 bytes crates/cargo-rustapi/src/commands/new.rs | 26 +- crates/cargo-rustapi/src/templates/full.rs | 8 +- crates/cargo-rustapi/src/templates/mod.rs | 2 +- crates/cargo-rustapi/src/templates/web.rs | 12 +- crates/cargo-rustapi/tests/cli_tests.rs | 6 +- crates/rustapi-core/src/handler.rs | 8 +- crates/rustapi-core/src/lib.rs | 11 +- crates/rustapi-macros/src/api_error.rs | 36 +- crates/rustapi-macros/src/derive_schema.rs | 14 +- crates/rustapi-macros/src/lib.rs | 18 +- crates/rustapi-rs/Cargo.toml | 145 +++-- crates/rustapi-rs/README.md | 5 + crates/rustapi-rs/src/lib.rs | 581 ++++++++++----------- docs/ARCHITECTURE.md | 45 +- docs/FEATURES.md | 49 +- docs/GETTING_STARTED.md | 59 ++- docs/README.md | 6 + 25 files changed, 702 insertions(+), 539 deletions(-) create mode 100644 .github/scripts/public_api_label_gate.sh create mode 100644 .github/workflows/public-api.yml create mode 100644 CONTRACT.md delete mode 100644 RELEASES.md create mode 100644 api/public/rustapi-rs.all-features.txt create mode 100644 api/public/rustapi-rs.default.txt diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3997a558..667f22a1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -24,6 +24,7 @@ Please include a summary of the changes and the related issue. Please also inclu - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules +- [ ] If `api/public/*` snapshots changed, this PR has `breaking` or `feature` label ## Related Issues @@ -44,4 +45,4 @@ Add screenshots to help explain your changes. ## Additional Notes -Add any other context about the pull request here. \ No newline at end of file +Add any other context about the pull request here. diff --git a/.github/scripts/public_api_label_gate.sh b/.github/scripts/public_api_label_gate.sh new file mode 100644 index 00000000..a9f6afcf --- /dev/null +++ b/.github/scripts/public_api_label_gate.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +base_sha="${BASE_SHA:?BASE_SHA is required}" +head_sha="${HEAD_SHA:?HEAD_SHA is required}" + +changed_snapshots="$( + git diff --name-only "$base_sha" "$head_sha" -- \ + api/public/rustapi-rs.default.txt \ + api/public/rustapi-rs.all-features.txt +)" + +if [[ -z "$changed_snapshots" ]]; then + echo "No public API snapshot changes detected." + exit 0 +fi + +labels=",${PR_LABELS:-}," +if [[ "$labels" == *",breaking,"* || "$labels" == *",feature,"* ]]; then + echo "Public API snapshots changed and required label is present." + exit 0 +fi + +echo "::error::Public API snapshots changed but PR is missing required label: breaking or feature." +echo "Changed snapshot files:" +echo "$changed_snapshots" +exit 1 diff --git a/.github/workflows/public-api.yml b/.github/workflows/public-api.yml new file mode 100644 index 00000000..efab4de4 --- /dev/null +++ b/.github/workflows/public-api.yml @@ -0,0 +1,55 @@ +name: Public API + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + pull-requests: read + +env: + CARGO_TERM_COLOR: always + +jobs: + snapshot: + name: Snapshot Drift + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-public-api + uses: taiki-e/install-action@cargo-public-api + + - name: Generate current snapshots + run: | + cargo public-api -p rustapi-rs -s > /tmp/rustapi-rs.default.txt + cargo public-api -p rustapi-rs --all-features -s > /tmp/rustapi-rs.all-features.txt + + - name: Check snapshot drift + run: | + diff -u api/public/rustapi-rs.default.txt /tmp/rustapi-rs.default.txt + diff -u api/public/rustapi-rs.all-features.txt /tmp/rustapi-rs.all-features.txt + + label-gate: + name: Label Gate + runs-on: ubuntu-latest + needs: snapshot + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Enforce labels when snapshots change + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.sha }} + PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} + run: | + bash .github/scripts/public_api_label_gate.sh diff --git a/CONTRACT.md b/CONTRACT.md new file mode 100644 index 00000000..2b028be0 --- /dev/null +++ b/CONTRACT.md @@ -0,0 +1,61 @@ +# RustAPI Contract + +This document defines compatibility guarantees for the RustAPI workspace. + +## 1. Scope + +- Stable public contract: + - `rustapi-rs` (facade crate) + - `cargo-rustapi` (CLI surface) +- Internal implementation crates (best-effort, no stability guarantee): + - `rustapi-core`, `rustapi-openapi`, `rustapi-validate`, `rustapi-macros` + - `rustapi-extras`, `rustapi-ws`, `rustapi-toon`, `rustapi-view`, `rustapi-grpc` + - `rustapi-testing`, `rustapi-jobs` + +Do not depend on internal crate APIs for long-term compatibility. + +## 2. SemVer Policy + +- `rustapi-rs` follows strict SemVer: + - Breaking changes: major + - Additive changes: minor + - Fixes/internal-only changes: patch +- Public API surface is tracked by committed snapshots: + - `api/public/rustapi-rs.default.txt` + - `api/public/rustapi-rs.all-features.txt` +- Pull requests that change snapshots must be labeled: + - `breaking` (if compatibility breaks) + - `feature` (if additive API surface change) + +## 3. MSRV Policy + +- Workspace MSRV is pinned to Rust `1.78`. +- MSRV increases are allowed only in minor or major releases. +- MSRV changes must be called out in changelog/release notes. +- Patch releases must not raise MSRV. + +## 4. Deprecation Policy + +- Deprecations are soft-first: + - `#[deprecated]` attribute + - explicit migration path in docs/release notes +- Minimum deprecation window before removal: 2 minor releases. +- Removals occur only in major releases. + +## 5. Feature Flag Policy + +- New facade feature names must follow taxonomy: + - `core-*` + - `protocol-*` + - `extras-*` +- Meta features: + - `core` (default) + - `protocol-all` + - `extras-all` + - `full` +- Legacy aliases may exist temporarily for migration but must be treated as deprecated and eventually removed on a published timeline. + +## 6. Internal Leakage Rule + +- Public facade signatures should not expose internal crate paths. +- Macro/runtime internals are allowed only via `rustapi_rs::__private` and are excluded from stability guarantees. diff --git a/README.md b/README.md index 364dff41..a90c2824 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,22 @@ async fn list_users() -> &'static str { "ok" } * ✅ **Multi-threaded Runtime** * ✅ **Zero Config** +## Feature Taxonomy (Stable) + +RustAPI now groups features into three namespaces: + +| Namespace | Purpose | Examples | +|:--|:--|:--| +| `core-*` | Core framework capabilities | `core-openapi`, `core-tracing`, `core-http3` | +| `protocol-*` | Optional protocol crates | `protocol-toon`, `protocol-ws`, `protocol-view`, `protocol-grpc` | +| `extras-*` | Optional production middleware/integrations | `extras-jwt`, `extras-cors`, `extras-rate-limit`, `extras-replay` | + +Meta features: +- `core` (default) +- `protocol-all` +- `extras-all` +- `full = core + protocol-all + extras-all` + ## ✨ Latest Release Highlights (v0.1.335) * ✅ **Dual-Stack Runtime**: Simultaneous HTTP/1.1 (TCP) and HTTP/3 (QUIC/UDP) support diff --git a/RELEASES.md b/RELEASES.md deleted file mode 100644 index 3e37c66b..00000000 --- a/RELEASES.md +++ /dev/null @@ -1,48 +0,0 @@ -# RustAPI Release History - -## v0.1.333 - Quick Wins + Must-Have Completion (2026-02-08) - -This release combines dependency surface cleanup, runtime completions, and documentation alignment in one focused quick-wins iteration. - -### Highlights - -- **True dual-stack runtime completed**: `RustApi::run_dual_stack` now runs HTTP/1.1 (TCP) and HTTP/3 (QUIC/UDP) simultaneously. -- **WebSocket permessage-deflate negotiation completed**: real extension parsing and parameter negotiation added for `Sec-WebSocket-Extensions`. -- **OpenAPI ref integrity coverage strengthened**: component traversal validation now includes response/requestBody/header/callback paths with tests. -- **Async validation context from app state**: `AsyncValidatedJson` now uses state-provided `ValidationContext` behavior with verified coverage. -- **Native OpenAPI + validation documentation alignment**: architecture docs are synced to OpenAPI 3.1 and v2-native validation direction. -- **Dependency footprint reduced (quick wins)**: unused/overly broad dependencies and feature sets were tightened, reducing lockfile surface. - -### Technical Details - -- `crates/rustapi-core/src/app.rs`: `run_dual_stack` implementation -- `crates/rustapi-core/src/server.rs`: `Server::from_shared` for shared app state -- `crates/rustapi-ws/src/upgrade.rs`: permessage-deflate parsing/negotiation -- `crates/rustapi-ws/src/compression.rs`: negotiation test updates -- `crates/rustapi-openapi/src/tests.rs`: reference traversal coverage test -- `docs/ARCHITECTURE.md`, `docs/cookbook/src/architecture/system_overview.md`, `crates/rustapi-openapi/README.md`: architecture/docs alignment - -### Validation - -- `cargo test -p rustapi-openapi` -- `cargo test -p rustapi-ws` -- `cargo test -p rustapi-core test_async_validated_json_with_state_context` -- `cargo check -p rustapi-core --features http3` - -### Commit References - -- `ca238ac` chore(quick-wins): reduce dependency surface and align native OpenAPI docs -- `dcb0e8b` feat(core/ws/openapi): complete quick-wins must-haves - ---- - -## v0.1.300 - Time-Travel Debugging (2026-02-06) - -- Replay system (record/replay/diff) -- Admin API + CLI support -- Security (token auth, redaction, TTL) - -## v0.1.202 - Performance Revolution (2026-01-26) - -- Broad performance optimizations in server and JSON layers -- Benchmark improvements and release profile tuning diff --git a/api/public/rustapi-rs.all-features.txt b/api/public/rustapi-rs.all-features.txt new file mode 100644 index 0000000000000000000000000000000000000000..71f82a40ae57a9511029edf7a42e6e84ce9ffba9 GIT binary patch literal 28810 zcmcIt+j1K>4CQlY`VW2XWBZorbS6=qNzz&Le}hbU*cORiP` znDHdBE;u{{Ne~3E%)kHKg%9CP_#xbcZCK&o9bWIlAzX&La2*zSy$|1q@9;{#$4{?D zzIQL&3%}rJh;ak7CEndamf!g?{E8n9>iw|6&o+EQ+pF*bSl3~VKetMD_hYZlAWt?s zC5L_Zf)nr?wh>V_q#}=RI`t$+P@50NdQ5}~DXl(^Hn)Z^+Q!u~5pF?;G z{$Ie~*7i9Nc96aVnuQ0o$Z@=c-VQ2+S=9bjlzWU3)wDi?=58QE=6n2H$8qEQTH`nJ z;h5-pRLA7Ix8W@i*9{oj#>&GXaks4&w!97R(R=DyJ3_QN)*j28ly#|T_Y9-;5qxPG zZZQL7e(4ZCWA;!k%`3h14(nkb{9GPJ&2L~OY~%MZ>ft8L(VxFz`#FlbRa!^*Bd+=b z&@{Sv6s^Pxt5j>P`jY2So};|PZ#o9FbgL*Y_MbYjB4~Smz(pt4i_4hd&#)LYUZFvvu~Z0Wj}d|78(B#4|HW3$G%^3iKvfuCiD@g0iMJ!<8Gz%XiFrE zpZ6Lqb7xL7oU=+Zn^KJHJw{U(OIgNQ^iDZ@Iq!CueIFw41x7&TGTYb1V3wE*jEKqp zz5pBgdD|GTOZ$jU7eq`aevJ-Aj$SJL#05q`tm-1ZqmQx?PaAV&Y>_sB-rXYVfg06C z<7kOi(4sDyL@AB}Th?eTmt%}IjhP06J^YCNYU7wmPJDaqd2AhftKlgUoDbgkNNiTvif2|xNfO4Kp>sX7x zT!FS-i&fmCf46aey$)Za#`F;w+C=Wsk`4HR5#vsuqJO%5l~}vz?b9AmhZ@Z>L%}_b zV`n|pa$PVZ$4CJC#C>jod0@2*j0-V3E~At8=2#Aqc|Y0PbvtE}p}Z%1*T-p_$;+`V zr;?hyHwIM0q@OIFl5P*FVbO!PW$$t{PbROA(>|*=jX%R0u=>m#2hM3?l=(}$x_ zmVfJ`$b8|xqw||`lry*I*GKbY*>a>VntX=2Y(1;UIM%aWxpm~}^jPIYpP6`ib4YaA zH%1P4%F^vXIj%iQIF51-j@;q&Iki4!x5G+wK$~A1L-S}$pLgrCNEx(Q154r0$K~8w zbsp)S3+& z;&isyYT5f1U7IBv=R+jF*FbjGbHDyXGzD$$qaALYsYbj@2*ud0XbK z&P{j1>|^A4HM^d3lvx7{r_ZwGz4Bz-`Z$*CTMfaQiJQWjHPN*%LpS8OgGI4s>vF6X znY%uoC5u-_@a}OfvU@qRes;~0=^IDmUh4Jrym`Bv9e;113FEBg={(QVSnXksVU7Lf zJ=SMnO6e5EWjxYa?OH=6n(Sy+ZFRW-KSB#_r!0Y;jbtX;aM5NQ@-gBQ9QN!(j^rO@ zUsOEja!Q1JDlUB$>wcv2Iv;0^9Ib`Do*-vXB9-rWSlVhAh73Ctvg=;?n6;{pDA^uE zmL44YhObw)C~?_`4u_0qMsH7pYm19cwcw8jPqjb`VTsI7;ilRNea!`9)?#5~wsrjY zI^lBnWA%$JM>fUUUy)#I!B(*_)fRoThW&Ru zEcNwJLw157>x3Su?Fra%RCx-g&Cb0xAhYzlhm*JBY{;*MN3QdjBl17@bZUDw9zH$U zD(gzV9S-$EPp4L~r`?T-L+?{pe^+BCD2ZE%sb$upT5rg643n(KKJ$z|z4cbrf@NQh zV@scof5+E1spiiyrs`PAJ_rW6ws_|17`~kQPJbFfKVLcL+tpH*eVN9WF`xckK4#i} zj@Ni{Q{&4Uf^2^Fw?`YR@bzkQ4A+(wtLedhCMwY^br<6(-kRhZ zGII%>>5L%jpWvWplU5(skf)xwWXBjwS)1#-E9lf#*tR8tc`Cl`BwI&s$1@?dvMQoQ zRxdOD`T%yC&p*>v>#Ahx{kGLt*Rkxeyc!v&TapFH}CcYId# zTVS5W5oR3Sa`#f%Z^7e9vBhmnC4Roc+v=T%jVLX7w)-3HMr>vNqbwZCDH5Zl5l> zcF(8ySU-mzdAlJ`Ua~8A@jUk_m9ywYtW1&ml!xHC!<-^dA4^MBUdhc?e_rCWigDJE z?e*<-G+UMX6qdHu{S>@b_I4g^j_roJI#0_TP&Fh=J>4-JuitCGwW-FcMWuL&dydm2 zpV+1q@!61bpvY6MZ9L!hc>X)AYWkzc@6{YJZ1-H%7+GrX$8oF`_@^PZ^9lIGHg`g< z6QcT~jQKO*ZK6XLaivL~iYIOF$)m(;r_T%MkZ~2?+`fQK@ZKU;&ZO>!FfY6GWJ4Pd zsN>ZJuf@|2rS@KhpNf0geR}MlzAR5s)L2RkCEXm~lA+gP_xtOq6`AgLt7!QvoM^6a zLz7RBuYvIfxydd32HqUzJ$aOPxz+V@_1$?_j?FS!#M5)8QyI5f3tc?YdOur<%DwOn zc1GRp`gI)39d5^wtj~LtsHrwsTXQ^)WUDf+;aP6YC~@>pG>u`j(>g0A-(qF>R;$1A zy6kBk(^hL;qH}IoZg3lB;yRpmV`6z0tJXu`6InGx?Q>aU80~b$@i<;3ueam2=f29m zvV!IocaX`>BehfH%lnWw$||*Sbhg5ba(XI@_rme!vsCfnTiK}(Q>`rEoyd;OyHVeP z(*Z2_KN)#XAv|B(ZcoC7zYM-svZx>;^hJr8`hC$p7V$>)@2p97iJi?oJ}l8U55YBzh*EYTggaSaMcaA^}6L z31@69^UrPdWy2PE)0XATGlnz9Jhj8m+c5uRhz$){6Jv~V`b^I@jZ!|VrdCIi8Do=w eu4k0AuVIvA#u(+F>ltZBFO5dc!llnT7XJa8FQt0` literal 0 HcmV?d00001 diff --git a/api/public/rustapi-rs.default.txt b/api/public/rustapi-rs.default.txt new file mode 100644 index 0000000000000000000000000000000000000000..0633c1f28a46f4dad107a9a2236ed2ede1402803 GIT binary patch literal 15508 zcmcJWL2p|(5QKRS(EreLjy(oNfmBrxw{g+fX?ipiNpTIxmMbYq-5=j}hRnkwMLsz@ z%fN^o+1lZ9xm=Q;`2PF%K70u8!nffzY{LpacX+)Ihp-6u;U-++^*($Pp5Rq_AOE_Z zc;B<|EPRizV~ty=E%DneZ26w2;Rk$KRG&w0=Fp(Z&S<+258)0TyoDbd^lF8l2lQeU zeuhU|{LSLak10&pxNvTP){j?<4%>=-;8wsePNm-*L?EFyc4nF@FJ@HF~}ZS`NO5p5KNG zsQiVQw1yY9xVII4i{p5Jnk5!5MT399ac(bChwimN1R7FS)by&kXB1IC^j zT!F2=>|n!vr!uqNpq}w8aD&QwEmns&sHFGs!B(3_E2W12jutnFSK70zq;V`y9}Aqi zteH?MsOOhI5Phy;LtZi$EIF`595&H+u1i;l3HKG-cvc+hpG}cj$XIYiws?};@uWJm zZqc}o?bAa~5P(c5H>Sw@MxLT^vYC7uX& zab>web>iO5;_)64TE_@Y?|x2E8j=;xP1a{iyMqnwJ)EV~>C0SlZDrkQ*?U<`OOrKq zSRd6f82YC(n%pxFuPJp>zwGQLSDCwOW~t_3I=Rw#melx8sm1EyVLgwlaSy|@xNV)y zb@j8xw2IDyN=+{tno{#b%g&5)FM9B!Tu)(1xxJ4oo|SO{bM+n3s3K&Yn)I zjsV9eT&Hw~9fT)p z3jZp7Qch!Sg+6Q5$hb5{*7nV|$1gQ<0;Z2ut}^RT;p|K;@0EwIl{yM*YbmJQ?J=!c zmstCF+_8_t*~41_dT_ejw~@Z7c|Iz5*SH=mFYjAPp)dQ( zJfZP`V;LJObF8g<#Kb?! zJ}#N}st2Q72T~h;4pg4UA7h*Mk*saNbvef^8B$t$Ip^0`n_{QlK2Pat_u%R{D7%hn z6)&Hyl|3x9rIvPEb|PA9{e+B8vy^x8ct`OccaDDz&3T-={NziyMOXLxC3@+*2XSSgZI*V69i*)ACiVT5l$vyS~I%jpfzKKB8CaDQBKq zMWcJQ4yl^T_}XeCqw{48JsY*GyzepJbn4euR@vW_S|aD(2rAsJ<s58{2~_vo8%9*ius zmrlRYQ^;x^_d%tnUslT6^2&49v)0pyYpuhbl71hwrAI}^e)@)}opB@25)TxAEp`dM59G;3W|! literal 0 HcmV?d00001 diff --git a/crates/cargo-rustapi/src/commands/new.rs b/crates/cargo-rustapi/src/commands/new.rs index ae6953d6..a7cb5170 100644 --- a/crates/cargo-rustapi/src/commands/new.rs +++ b/crates/cargo-rustapi/src/commands/new.rs @@ -91,14 +91,14 @@ pub async fn new_project(mut args: NewArgs) -> Result<()> { vec![] } else { let available = [ - "jwt", - "cors", - "rate-limit", - "config", - "toon", - "ws", - "view", - "grpc", + "extras-jwt", + "extras-cors", + "extras-rate-limit", + "extras-config", + "protocol-toon", + "protocol-ws", + "protocol-view", + "protocol-grpc", ]; let defaults = match template { ProjectTemplate::Full => vec![true, true, true, true, false, false, false, false], @@ -183,12 +183,10 @@ pub async fn new_project(mut args: NewArgs) -> Result<()> { style("http://localhost:8080").cyan() ); - if features.iter().any(|f| f == "swagger-ui") || template == ProjectTemplate::Full { - println!( - "API docs available at {}", - style("http://localhost:8080/docs").cyan() - ); - } + println!( + "API docs available at {}", + style("http://localhost:8080/docs").cyan() + ); Ok(()) } diff --git a/crates/cargo-rustapi/src/templates/full.rs b/crates/cargo-rustapi/src/templates/full.rs index 50b8a90a..d2adf498 100644 --- a/crates/cargo-rustapi/src/templates/full.rs +++ b/crates/cargo-rustapi/src/templates/full.rs @@ -7,10 +7,10 @@ use tokio::fs; pub async fn generate(name: &str, features: &[String]) -> Result<()> { // Add recommended features for full template let mut all_features: Vec = vec![ - "jwt".to_string(), - "cors".to_string(), - "rate-limit".to_string(), - "config".to_string(), + "extras-jwt".to_string(), + "extras-cors".to_string(), + "extras-rate-limit".to_string(), + "extras-config".to_string(), ]; // Add user-specified features diff --git a/crates/cargo-rustapi/src/templates/mod.rs b/crates/cargo-rustapi/src/templates/mod.rs index 4f1138b1..34ecfc12 100644 --- a/crates/cargo-rustapi/src/templates/mod.rs +++ b/crates/cargo-rustapi/src/templates/mod.rs @@ -77,7 +77,7 @@ RUSTAPI_ENV=development # Database (if using sqlx) # DATABASE_URL=postgres://user:pass@localhost/db -# JWT Secret (if using jwt feature) +# JWT Secret (if using extras-jwt feature) # JWT_SECRET=your-secret-key-here # Logging diff --git a/crates/cargo-rustapi/src/templates/web.rs b/crates/cargo-rustapi/src/templates/web.rs index 90b73e55..33774dc9 100644 --- a/crates/cargo-rustapi/src/templates/web.rs +++ b/crates/cargo-rustapi/src/templates/web.rs @@ -5,13 +5,13 @@ use anyhow::Result; use tokio::fs; pub async fn generate(name: &str, features: &[String]) -> Result<()> { - // Add view feature + // Add the protocol-view feature for template rendering support let mut all_features = features.to_vec(); - if !all_features.contains(&"view".to_string()) { - all_features.push("view".to_string()); + if !all_features.contains(&"protocol-view".to_string()) { + all_features.push("protocol-view".to_string()); } - // Cargo.toml - rustapi-view is accessed through rustapi-rs when "view" feature is enabled + // Cargo.toml - rustapi-view is accessed through rustapi-rs when protocol-view is enabled let cargo_toml = format!( r#"[package] name = "{name}" @@ -39,7 +39,7 @@ tracing-subscriber = {{ version = "0.3", features = ["env-filter"] }} let main_rs = r#"mod handlers; use rustapi_rs::prelude::*; -use rustapi_rs::view::Templates; +use rustapi_rs::protocol::view::Templates; #[rustapi_rs::main] async fn main() -> Result<(), Box> { @@ -76,7 +76,7 @@ async fn main() -> Result<(), Box> { let handlers_mod = r#"//! Page handlers use rustapi_rs::prelude::*; -use rustapi_rs::view::{Templates, View}; +use rustapi_rs::protocol::view::{Templates, View}; use serde::Serialize; #[derive(Serialize)] diff --git a/crates/cargo-rustapi/tests/cli_tests.rs b/crates/cargo-rustapi/tests/cli_tests.rs index 0e934ff3..28f13bee 100644 --- a/crates/cargo-rustapi/tests/cli_tests.rs +++ b/crates/cargo-rustapi/tests/cli_tests.rs @@ -90,7 +90,7 @@ mod new_command { "--template", "minimal", "--features", - "jwt,cors", + "extras-jwt,extras-cors", "--yes", ]) .assert() @@ -99,8 +99,8 @@ mod new_command { let cargo_content = fs::read_to_string(project_path.join("Cargo.toml")).expect("Failed to read Cargo.toml"); assert!( - cargo_content.contains("jwt") && cargo_content.contains("cors"), - "Cargo.toml should include jwt and cors features" + cargo_content.contains("extras-jwt") && cargo_content.contains("extras-cors"), + "Cargo.toml should include extras-jwt and extras-cors features" ); } diff --git a/crates/rustapi-core/src/handler.rs b/crates/rustapi-core/src/handler.rs index 3acb2110..6ac3600b 100644 --- a/crates/rustapi-core/src/handler.rs +++ b/crates/rustapi-core/src/handler.rs @@ -62,8 +62,14 @@ use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; +mod sealed { + pub trait Sealed {} + + impl Sealed for T where T: Clone + Send + Sync + Sized + 'static {} +} + /// Trait representing an async handler function -pub trait Handler: Clone + Send + Sync + Sized + 'static { +pub trait Handler: sealed::Sealed + Clone + Send + Sync + Sized + 'static { /// The response type type Future: Future + Send + 'static; diff --git a/crates/rustapi-core/src/lib.rs b/crates/rustapi-core/src/lib.rs index c0d0e939..97558d3e 100644 --- a/crates/rustapi-core/src/lib.rs +++ b/crates/rustapi-core/src/lib.rs @@ -51,9 +51,9 @@ //! full framework experience with all features and re-exports. mod app; -pub mod auto_route; +mod auto_route; pub use auto_route::collect_auto_routes; -pub mod auto_schema; +mod auto_schema; pub use auto_schema::apply_auto_schemas; mod error; mod extract; @@ -63,11 +63,11 @@ pub mod health; #[cfg(feature = "http3")] pub mod http3; pub mod interceptor; -pub mod json; +pub(crate) mod json; pub mod middleware; pub mod multipart; -pub mod path_params; -pub mod path_validation; +pub(crate) mod path_params; +pub(crate) mod path_validation; #[cfg(feature = "replay")] pub mod replay; mod request; @@ -121,6 +121,7 @@ pub use middleware::{BodyLimitLayer, RequestId, RequestIdLayer, TracingLayer, DE #[cfg(feature = "metrics")] pub use middleware::{MetricsLayer, MetricsResponse}; pub use multipart::{Multipart, MultipartConfig, MultipartField, UploadedFile}; +pub use path_params::PathParams; pub use request::{BodyVariant, Request}; pub use response::{ Body as ResponseBody, Created, Html, IntoResponse, NoContent, Redirect, Response, WithStatus, diff --git a/crates/rustapi-macros/src/api_error.rs b/crates/rustapi-macros/src/api_error.rs index 0b6066c5..23076ef6 100644 --- a/crates/rustapi-macros/src/api_error.rs +++ b/crates/rustapi-macros/src/api_error.rs @@ -1,9 +1,37 @@ +use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput, Expr, Lit, Meta}; +fn get_core_path() -> proc_macro2::TokenStream { + let rustapi_rs_found = crate_name("rustapi-rs").or_else(|_| crate_name("rustapi_rs")); + + if let Ok(found) = rustapi_rs_found { + match found { + FoundCrate::Itself => quote! { ::rustapi_rs::__private::core }, + FoundCrate::Name(name) => { + let normalized = name.replace('-', "_"); + let ident = syn::Ident::new(&normalized, proc_macro2::Span::call_site()); + quote! { ::#ident::__private::core } + } + } + } else if let Ok(found) = crate_name("rustapi-core").or_else(|_| crate_name("rustapi_core")) { + match found { + FoundCrate::Itself => quote! { crate }, + FoundCrate::Name(name) => { + let normalized = name.replace('-', "_"); + let ident = syn::Ident::new(&normalized, proc_macro2::Span::call_site()); + quote! { ::#ident } + } + } + } else { + quote! { ::rustapi_core } + } +} + pub fn expand_derive_api_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; + let core_path = get_core_path(); let variants = match &input.data { Data::Enum(data) => &data.variants, @@ -63,8 +91,8 @@ pub fn expand_derive_api_error(input: proc_macro::TokenStream) -> proc_macro::To match_arms.push(quote! { #name::#variant_name => { - ::rustapi_core::ApiError::new( - ::rustapi_core::StatusCode::from_u16(#status).unwrap(), + #core_path::ApiError::new( + #core_path::StatusCode::from_u16(#status).unwrap(), #code, #message ).into_response() @@ -73,8 +101,8 @@ pub fn expand_derive_api_error(input: proc_macro::TokenStream) -> proc_macro::To } let expanded = quote! { - impl ::rustapi_core::IntoResponse for #name { - fn into_response(self) -> ::rustapi_core::Response { + impl #core_path::IntoResponse for #name { + fn into_response(self) -> #core_path::Response { match self { #(#match_arms)* } diff --git a/crates/rustapi-macros/src/derive_schema.rs b/crates/rustapi-macros/src/derive_schema.rs index d6f672ba..793f03aa 100644 --- a/crates/rustapi-macros/src/derive_schema.rs +++ b/crates/rustapi-macros/src/derive_schema.rs @@ -6,7 +6,7 @@ use syn::{Data, DataEnum, DataStruct, Fields, Ident}; /// Determine the path to rustapi_openapi module based on the user's dependencies. /// /// This function checks if the user's Cargo.toml has: -/// 1. `rustapi-rs` - use `::rustapi_rs::prelude::rustapi_openapi` +/// 1. `rustapi-rs` - use `::rustapi_rs::__private::openapi` /// 2. `rustapi-openapi` - use `::rustapi_openapi` directly /// /// This allows the Schema derive macro to work in both: @@ -21,13 +21,13 @@ fn get_openapi_path() -> TokenStream { match found { FoundCrate::Itself => { // We're in rustapi-rs itself - quote! { crate::prelude::rustapi_openapi } + quote! { ::rustapi_rs::__private::openapi } } FoundCrate::Name(name) => { // Normalize to underscore for use in code let normalized = name.replace('-', "_"); let ident = syn::Ident::new(&normalized, proc_macro2::Span::call_site()); - quote! { ::#ident::prelude::rustapi_openapi } + quote! { ::#ident::__private::openapi } } } } else if let Ok(found) = @@ -47,11 +47,11 @@ fn get_openapi_path() -> TokenStream { } } else { // Default fallback - assume rustapi_rs is available (most common case) - quote! { ::rustapi_rs::prelude::rustapi_openapi } + quote! { ::rustapi_rs::__private::openapi } } } -/// Get serde_json path - either from rustapi_rs::prelude or directly +/// Get serde_json path - either from rustapi_rs::__private or directly fn get_serde_json_path() -> TokenStream { // Try both hyphenated and underscored versions let rustapi_rs_found = crate_name("rustapi-rs").or_else(|_| crate_name("rustapi_rs")); @@ -59,12 +59,12 @@ fn get_serde_json_path() -> TokenStream { if let Ok(found) = rustapi_rs_found { match found { FoundCrate::Itself => { - quote! { crate::prelude::serde_json } + quote! { ::rustapi_rs::__private::serde_json } } FoundCrate::Name(name) => { let normalized = name.replace('-', "_"); let ident = syn::Ident::new(&normalized, proc_macro2::Span::call_site()); - quote! { ::#ident::prelude::serde_json } + quote! { ::#ident::__private::serde_json } } } } else { diff --git a/crates/rustapi-macros/src/lib.rs b/crates/rustapi-macros/src/lib.rs index 60321c5e..bce8c1d7 100644 --- a/crates/rustapi-macros/src/lib.rs +++ b/crates/rustapi-macros/src/lib.rs @@ -116,8 +116,8 @@ pub fn schema(_attr: TokenStream, item: TokenStream) -> TokenStream { #[allow(non_upper_case_globals)] #[#rustapi_path::__private::linkme::distributed_slice(#rustapi_path::__private::AUTO_SCHEMAS)] #[linkme(crate = #rustapi_path::__private::linkme)] - static #registrar_ident: fn(&mut #rustapi_path::__private::rustapi_openapi::OpenApiSpec) = - |spec: &mut #rustapi_path::__private::rustapi_openapi::OpenApiSpec| { + static #registrar_ident: fn(&mut #rustapi_path::__private::openapi::OpenApiSpec) = + |spec: &mut #rustapi_path::__private::openapi::OpenApiSpec| { spec.register_in_place::<#ident>(); }; }; @@ -607,7 +607,7 @@ fn generate_route_handler(method: &str, attr: TokenStream, item: TokenStream) -> // Auto-register referenced schemas with linkme (best-effort) #[doc(hidden)] #[allow(non_snake_case)] - fn #schema_reg_fn_name(spec: &mut #rustapi_path::__private::rustapi_openapi::OpenApiSpec) { + fn #schema_reg_fn_name(spec: &mut #rustapi_path::__private::openapi::OpenApiSpec) { #( spec.register_in_place::<#schema_types>(); )* } @@ -615,7 +615,7 @@ fn generate_route_handler(method: &str, attr: TokenStream, item: TokenStream) -> #[allow(non_upper_case_globals)] #[#rustapi_path::__private::linkme::distributed_slice(#rustapi_path::__private::AUTO_SCHEMAS)] #[linkme(crate = #rustapi_path::__private::linkme)] - static #auto_schema_name: fn(&mut #rustapi_path::__private::rustapi_openapi::OpenApiSpec) = #schema_reg_fn_name; + static #auto_schema_name: fn(&mut #rustapi_path::__private::openapi::OpenApiSpec) = #schema_reg_fn_name; }; debug_output(&format!("{} {}", method, path_value), &expanded); @@ -969,12 +969,12 @@ fn get_validate_path() -> proc_macro2::TokenStream { if let Ok(found) = rustapi_rs_found { match found { FoundCrate::Itself => { - quote! { crate::__private::rustapi_validate } + quote! { ::rustapi_rs::__private::validate } } FoundCrate::Name(name) => { let normalized = name.replace('-', "_"); let ident = syn::Ident::new(&normalized, proc_macro2::Span::call_site()); - quote! { ::#ident::__private::rustapi_validate } + quote! { ::#ident::__private::validate } } } } else if let Ok(found) = @@ -1004,11 +1004,11 @@ fn get_core_path() -> proc_macro2::TokenStream { if let Ok(found) = rustapi_rs_found { match found { - FoundCrate::Itself => quote! { crate }, + FoundCrate::Itself => quote! { ::rustapi_rs::__private::core }, FoundCrate::Name(name) => { let normalized = name.replace('-', "_"); let ident = syn::Ident::new(&normalized, proc_macro2::Span::call_site()); - quote! { ::#ident } + quote! { ::#ident::__private::core } } } } else if let Ok(found) = crate_name("rustapi-core").or_else(|_| crate_name("rustapi_core")) { @@ -1036,7 +1036,7 @@ fn get_async_trait_path() -> proc_macro2::TokenStream { if let Ok(found) = rustapi_rs_found { match found { FoundCrate::Itself => { - quote! { crate::__private::async_trait } + quote! { ::rustapi_rs::__private::async_trait } } FoundCrate::Name(name) => { let normalized = name.replace('-', "_"); diff --git a/crates/rustapi-rs/Cargo.toml b/crates/rustapi-rs/Cargo.toml index 6ecfaf0d..b9b610b1 100644 --- a/crates/rustapi-rs/Cargo.toml +++ b/crates/rustapi-rs/Cargo.toml @@ -39,56 +39,105 @@ doc-comment = "0.3" uuid = { workspace = true, features = ["serde", "v4"] } [features] -default = ["swagger-ui", "tracing"] -swagger-ui = ["rustapi-core/swagger-ui", "rustapi-openapi/swagger-ui"] +default = ["core"] -# Performance features -simd-json = ["rustapi-core/simd-json"] -tracing = ["rustapi-core/tracing"] -legacy-validator = ["dep:validator", "rustapi-core/legacy-validator"] +# Canonical core features +core = ["core-openapi", "core-tracing"] +core-openapi = ["rustapi-core/swagger-ui", "rustapi-openapi/swagger-ui"] +core-tracing = ["rustapi-core/tracing"] +core-simd-json = ["rustapi-core/simd-json"] +core-legacy-validator = ["dep:validator", "rustapi-core/legacy-validator"] +core-compression = ["rustapi-core/compression"] +core-compression-brotli = ["rustapi-core/compression-brotli"] +core-cookies = ["dep:rustapi-extras", "rustapi-extras/cookies", "rustapi-core/cookies"] +core-http3 = ["rustapi-core/http3"] +core-http3-dev = ["rustapi-core/http3-dev"] -# Compression middleware -compression = ["rustapi-core/compression"] -compression-brotli = ["rustapi-core/compression-brotli"] +# Canonical protocol features +protocol-toon = ["dep:rustapi-toon"] +protocol-ws = ["dep:rustapi-ws"] +protocol-view = ["dep:rustapi-view"] +protocol-grpc = ["dep:rustapi-grpc"] +protocol-http3 = ["core-http3"] +protocol-http3-dev = ["core-http3-dev"] +protocol-all = ["protocol-toon", "protocol-ws", "protocol-view", "protocol-grpc"] -# Security and utility features (from rustapi-extras) -jwt = ["dep:rustapi-extras", "rustapi-extras/jwt"] -cors = ["dep:rustapi-extras", "rustapi-extras/cors"] -rate-limit = ["dep:rustapi-extras", "rustapi-extras/rate-limit"] -config = ["dep:rustapi-extras", "rustapi-extras/config"] -cookies = ["dep:rustapi-extras", "rustapi-extras/cookies", "rustapi-core/cookies"] -sqlx = ["dep:rustapi-extras", "rustapi-extras/sqlx"] -insight = ["dep:rustapi-extras", "rustapi-extras/insight"] +# Canonical extras features +extras-jwt = ["dep:rustapi-extras", "rustapi-extras/jwt"] +extras-cors = ["dep:rustapi-extras", "rustapi-extras/cors"] +extras-rate-limit = ["dep:rustapi-extras", "rustapi-extras/rate-limit"] +extras-config = ["dep:rustapi-extras", "rustapi-extras/config"] +extras-sqlx = ["dep:rustapi-extras", "rustapi-extras/sqlx"] +extras-insight = ["dep:rustapi-extras", "rustapi-extras/insight"] +extras-timeout = ["dep:rustapi-extras", "rustapi-extras/timeout"] +extras-guard = ["dep:rustapi-extras", "rustapi-extras/guard"] +extras-logging = ["dep:rustapi-extras", "rustapi-extras/logging"] +extras-circuit-breaker = ["dep:rustapi-extras", "rustapi-extras/circuit-breaker"] +extras-retry = ["dep:rustapi-extras", "rustapi-extras/retry"] +extras-security-headers = ["dep:rustapi-extras", "rustapi-extras/security-headers"] +extras-api-key = ["dep:rustapi-extras", "rustapi-extras/api-key"] +extras-cache = ["dep:rustapi-extras", "rustapi-extras/cache"] +extras-dedup = ["dep:rustapi-extras", "rustapi-extras/dedup"] +extras-sanitization = ["dep:rustapi-extras", "rustapi-extras/sanitization"] +extras-otel = ["dep:rustapi-extras", "rustapi-extras/otel"] +extras-structured-logging = ["dep:rustapi-extras", "rustapi-extras/structured-logging"] +extras-replay = ["dep:rustapi-extras", "rustapi-extras/replay"] +extras-all = [ + "extras-jwt", + "extras-cors", + "extras-rate-limit", + "extras-config", + "extras-sqlx", + "extras-insight", + "extras-timeout", + "extras-guard", + "extras-logging", + "extras-circuit-breaker", + "extras-retry", + "extras-security-headers", + "extras-api-key", + "extras-cache", + "extras-dedup", + "extras-sanitization", + "extras-otel", + "extras-structured-logging", + "extras-replay", +] -# TOON format support -toon = ["dep:rustapi-toon"] +# Legacy feature aliases (kept for migration compatibility) +swagger-ui = ["core-openapi"] +tracing = ["core-tracing"] +simd-json = ["core-simd-json"] +legacy-validator = ["core-legacy-validator"] +compression = ["core-compression"] +compression-brotli = ["core-compression-brotli"] +cookies = ["core-cookies"] +http3 = ["protocol-http3"] +http3-dev = ["protocol-http3-dev"] +toon = ["protocol-toon"] +ws = ["protocol-ws"] +view = ["protocol-view"] +grpc = ["protocol-grpc"] +jwt = ["extras-jwt"] +cors = ["extras-cors"] +rate-limit = ["extras-rate-limit"] +config = ["extras-config"] +sqlx = ["extras-sqlx"] +insight = ["extras-insight"] +timeout = ["extras-timeout"] +guard = ["extras-guard"] +logging = ["extras-logging"] +circuit-breaker = ["extras-circuit-breaker"] +retry = ["extras-retry"] +security-headers = ["extras-security-headers"] +api-key = ["extras-api-key"] +cache = ["extras-cache"] +dedup = ["extras-dedup"] +sanitization = ["extras-sanitization"] +otel = ["extras-otel"] +structured-logging = ["extras-structured-logging"] +replay = ["extras-replay"] +extras = ["extras-jwt", "extras-cors", "extras-rate-limit"] -# WebSocket support -ws = ["dep:rustapi-ws"] - -# Template engine support -view = ["dep:rustapi-view"] - -# gRPC support -grpc = ["dep:rustapi-grpc"] - -# New Phase 11 & Observability features -timeout = ["dep:rustapi-extras", "rustapi-extras/timeout"] -guard = ["dep:rustapi-extras", "rustapi-extras/guard"] -logging = ["dep:rustapi-extras", "rustapi-extras/logging"] -circuit-breaker = ["dep:rustapi-extras", "rustapi-extras/circuit-breaker"] -retry = ["dep:rustapi-extras", "rustapi-extras/retry"] -security-headers = ["dep:rustapi-extras", "rustapi-extras/security-headers"] -api-key = ["dep:rustapi-extras", "rustapi-extras/api-key"] -cache = ["dep:rustapi-extras", "rustapi-extras/cache"] -dedup = ["dep:rustapi-extras", "rustapi-extras/dedup"] -sanitization = ["dep:rustapi-extras", "rustapi-extras/sanitization"] -otel = ["dep:rustapi-extras", "rustapi-extras/otel"] -structured-logging = ["dep:rustapi-extras", "rustapi-extras/structured-logging"] - -# Replay (time-travel debugging) -replay = ["dep:rustapi-extras", "rustapi-extras/replay"] - -# Meta features -extras = ["jwt", "cors", "rate-limit"] -full = ["extras", "config", "cookies", "sqlx", "toon", "insight", "compression", "ws", "view", "grpc", "timeout", "guard", "logging", "circuit-breaker", "security-headers", "api-key", "cache", "dedup", "sanitization", "otel", "structured-logging", "replay", "legacy-validator"] +# Canonical aggregate +full = ["core", "protocol-all", "extras-all", "core-legacy-validator"] diff --git a/crates/rustapi-rs/README.md b/crates/rustapi-rs/README.md index 13754d34..901421fd 100644 --- a/crates/rustapi-rs/README.md +++ b/crates/rustapi-rs/README.md @@ -37,6 +37,11 @@ We believe that writing high-performance, type-safe web APIs in Rust shouldn't r - **Templating**: Tera view engine. - **Jobs**: Background task processing (Redis/Postgres). +Feature taxonomy on the facade: +- `core-*`: core runtime and HTTP behavior (`core-openapi`, `core-tracing`, etc.) +- `protocol-*`: optional protocol crates (`protocol-toon`, `protocol-ws`, `protocol-view`, `protocol-grpc`) +- `extras-*`: optional production middleware/integrations (`extras-jwt`, `extras-cors`, etc.) + ## 📦 Quick Start Add `rustapi-rs` to your `Cargo.toml`. diff --git a/crates/rustapi-rs/src/lib.rs b/crates/rustapi-rs/src/lib.rs index 8daadf4b..1f818f18 100644 --- a/crates/rustapi-rs/src/lib.rs +++ b/crates/rustapi-rs/src/lib.rs @@ -1,386 +1,334 @@ //! # RustAPI //! -//! A FastAPI-like web framework for Rust. -//! -//! RustAPI combines Rust's performance and safety with FastAPI's "just write business logic" -//! approach. It provides automatic OpenAPI documentation, declarative validation, and -//! a developer-friendly experience. -//! -//! ## Quick Start -//! -//! ```rust,ignore -//! use rustapi_rs::prelude::*; -//! -//! #[derive(Serialize, Schema)] -//! struct Hello { -//! message: String, -//! } -//! -//! async fn hello() -> Json { -//! Json(Hello { -//! message: "Hello, World!".to_string(), -//! }) -//! } -//! -//! #[tokio::main] -//! async fn main() -> std::result::Result<(), Box> { -//! RustApi::new() -//! .route("/", get(hello)) -//! .run("127.0.0.1:8080") -//! .await -//! } -//! ``` -//! -//! ## Features -//! -//! - **DX-First**: Minimal boilerplate, intuitive API -//! - **Type-Safe**: Compile-time route and schema validation -//! - **Auto Documentation**: OpenAPI + Swagger UI out of the box -//! - **Declarative Validation**: Pydantic-style validation on structs -//! - **Batteries Included**: JWT, CORS, rate limiting (optional features) -//! -//! ## Optional Features -//! -//! Enable these features in your `Cargo.toml`: -//! -//! - `jwt` - JWT authentication middleware and `AuthUser` extractor -//! - `cors` - CORS middleware with builder pattern configuration -//! - `rate-limit` - IP-based rate limiting middleware -//! - `config` - Configuration management with `.env` file support -//! - `cookies` - Cookie parsing extractor -//! - `sqlx` - SQLx database error conversion to ApiError -//! - `grpc` - Tonic-based gRPC integration helpers -//! - `legacy-validator` - Compatibility mode for `validator::Validate` -//! - `extras` - Meta feature enabling jwt, cors, and rate-limit -//! - `full` - All optional features enabled -//! -//! ```toml -//! [dependencies] -//! rustapi-rs = { version = "0.1.300", features = ["jwt", "cors"] } -//! ``` +//! Public facade crate for RustAPI. -// Allow proc-macro expansions to refer to this crate via `::rustapi_rs`. extern crate self as rustapi_rs; -// Re-export core functionality -pub use rustapi_core::*; - -// Re-export macros +// Re-export all procedural macros from rustapi-macros. pub use rustapi_macros::*; -/// Private module for macro internals - DO NOT USE DIRECTLY -/// -/// This module re-exports internal crates needed by derive macros. -/// It shadows rustapi_core::__private from the glob re-export above. +/// Macro/runtime internals. Not part of the public compatibility contract. #[doc(hidden)] pub mod __private { pub use async_trait; - pub use rustapi_core::__private::*; + pub use rustapi_core as core; + pub use rustapi_core::__private::{linkme, AUTO_ROUTES, AUTO_SCHEMAS}; + pub use rustapi_openapi as openapi; + pub use rustapi_validate as validate; + pub use serde_json; } -// Re-export extras (feature-gated) -#[cfg(feature = "jwt")] -pub use rustapi_extras::jwt; -#[cfg(feature = "jwt")] -pub use rustapi_extras::{ - create_token, AuthUser, JwtError, JwtLayer, JwtValidation, ValidatedClaims, -}; +/// Stable core surface exposed by the facade. +pub mod core { + pub use rustapi_core::collect_auto_routes; + pub use rustapi_core::validation::Validatable; + pub use rustapi_core::{ + delete, delete_route, get, get_route, patch, patch_route, post, post_route, put, put_route, + route, serve_dir, sse_response, ApiError, AsyncValidatedJson, Body, BodyLimitLayer, + BodyStream, BodyVariant, ClientIp, Created, Environment, Extension, FieldError, + FromRequest, FromRequestParts, Handler, HandlerService, HeaderValue, Headers, Html, + IntoResponse, Json, KeepAlive, MethodRouter, Multipart, MultipartConfig, MultipartField, + NoContent, Path, Query, Redirect, Request, RequestId, RequestIdLayer, Response, + ResponseBody, Result, Route, RouteHandler, RouteMatch, Router, RustApi, RustApiConfig, Sse, + SseEvent, State, StaticFile, StaticFileConfig, StatusCode, StreamBody, TracingLayer, Typed, + TypedPath, UploadedFile, ValidatedJson, WithStatus, + }; -#[cfg(feature = "cors")] -pub use rustapi_extras::cors; -#[cfg(feature = "cors")] -pub use rustapi_extras::{AllowedOrigins, CorsLayer}; + pub use rustapi_core::get_environment; -#[cfg(feature = "rate-limit")] -pub use rustapi_extras::rate_limit; -#[cfg(feature = "rate-limit")] -pub use rustapi_extras::RateLimitLayer; + #[cfg(any(feature = "core-cookies", feature = "cookies"))] + pub use rustapi_core::Cookies; -#[cfg(feature = "config")] -pub use rustapi_extras::config; -#[cfg(feature = "config")] -pub use rustapi_extras::{ - env_or, env_parse, load_dotenv, load_dotenv_from, require_env, Config, ConfigError, Environment, -}; + #[cfg(any(feature = "core-compression", feature = "compression"))] + pub use rustapi_core::CompressionLayer; -#[cfg(feature = "sqlx")] -pub use rustapi_extras::{convert_sqlx_error, SqlxErrorExt}; + #[cfg(any(feature = "core-compression", feature = "compression"))] + pub use rustapi_core::middleware::{CompressionAlgorithm, CompressionConfig}; -// Re-export Phase 11 & Observability Features -#[cfg(feature = "timeout")] -pub use rustapi_extras::timeout; + #[cfg(any(feature = "core-http3", feature = "protocol-http3", feature = "http3"))] + pub use rustapi_core::{Http3Config, Http3Server}; +} -#[cfg(feature = "guard")] -pub use rustapi_extras::guard; +// Backward-compatible root re-exports. +pub use core::*; -#[cfg(feature = "logging")] -pub use rustapi_extras::logging; +/// Optional protocol integrations grouped under a stable namespace. +pub mod protocol { + #[cfg(any(feature = "protocol-toon", feature = "toon"))] + pub mod toon { + pub use rustapi_toon::*; + } -#[cfg(feature = "circuit-breaker")] -pub use rustapi_extras::circuit_breaker; + #[cfg(any(feature = "protocol-ws", feature = "ws"))] + pub mod ws { + pub use rustapi_ws::*; + } -#[cfg(feature = "retry")] -pub use rustapi_extras::retry; + #[cfg(any(feature = "protocol-view", feature = "view"))] + pub mod view { + pub use rustapi_view::*; + } -#[cfg(feature = "security-headers")] -pub use rustapi_extras::security_headers; + #[cfg(any(feature = "protocol-grpc", feature = "grpc"))] + pub mod grpc { + pub use rustapi_grpc::*; + } -#[cfg(feature = "api-key")] -pub use rustapi_extras::api_key; + #[cfg(any(feature = "core-http3", feature = "protocol-http3", feature = "http3"))] + pub mod http3 { + pub use rustapi_core::{Http3Config, Http3Server}; + } +} -#[cfg(feature = "cache")] -pub use rustapi_extras::cache; +/// Optional extras grouped under a stable namespace. +pub mod extras { + #[cfg(any(feature = "extras-jwt", feature = "jwt"))] + pub mod jwt { + pub use rustapi_extras::jwt; + pub use rustapi_extras::{ + create_token, AuthUser, JwtError, JwtLayer, JwtValidation, ValidatedClaims, + }; + } -#[cfg(feature = "dedup")] -pub use rustapi_extras::dedup; + #[cfg(any(feature = "extras-cors", feature = "cors"))] + pub mod cors { + pub use rustapi_extras::cors; + pub use rustapi_extras::{AllowedOrigins, CorsLayer}; + } -#[cfg(feature = "sanitization")] -pub use rustapi_extras::sanitization; + #[cfg(any(feature = "extras-rate-limit", feature = "rate-limit"))] + pub mod rate_limit { + pub use rustapi_extras::rate_limit; + pub use rustapi_extras::RateLimitLayer; + } -#[cfg(feature = "otel")] -pub use rustapi_extras::otel; + #[cfg(any(feature = "extras-config", feature = "config"))] + pub mod config { + pub use rustapi_extras::config; + pub use rustapi_extras::{ + env_or, env_parse, load_dotenv, load_dotenv_from, require_env, Config, ConfigError, + Environment, + }; + } -#[cfg(feature = "structured-logging")] -pub use rustapi_extras::structured_logging; + #[cfg(any(feature = "extras-sqlx", feature = "sqlx"))] + pub mod sqlx { + pub use rustapi_extras::{convert_sqlx_error, SqlxErrorExt}; + } -// Replay (time-travel debugging) -#[cfg(feature = "replay")] -pub use rustapi_extras::replay; + #[cfg(any(feature = "extras-insight", feature = "insight"))] + pub mod insight { + pub use rustapi_extras::insight; + } + + #[cfg(any(feature = "extras-timeout", feature = "timeout"))] + pub mod timeout { + pub use rustapi_extras::timeout; + } + + #[cfg(any(feature = "extras-guard", feature = "guard"))] + pub mod guard { + pub use rustapi_extras::guard; + } + + #[cfg(any(feature = "extras-logging", feature = "logging"))] + pub mod logging { + pub use rustapi_extras::logging; + } -// Re-export TOON (feature-gated) -#[cfg(feature = "toon")] + #[cfg(any(feature = "extras-circuit-breaker", feature = "circuit-breaker"))] + pub mod circuit_breaker { + pub use rustapi_extras::circuit_breaker; + } + + #[cfg(any(feature = "extras-retry", feature = "retry"))] + pub mod retry { + pub use rustapi_extras::retry; + } + + #[cfg(any(feature = "extras-security-headers", feature = "security-headers"))] + pub mod security_headers { + pub use rustapi_extras::security_headers; + } + + #[cfg(any(feature = "extras-api-key", feature = "api-key"))] + pub mod api_key { + pub use rustapi_extras::api_key; + } + + #[cfg(any(feature = "extras-cache", feature = "cache"))] + pub mod cache { + pub use rustapi_extras::cache; + } + + #[cfg(any(feature = "extras-dedup", feature = "dedup"))] + pub mod dedup { + pub use rustapi_extras::dedup; + } + + #[cfg(any(feature = "extras-sanitization", feature = "sanitization"))] + pub mod sanitization { + pub use rustapi_extras::sanitization; + } + + #[cfg(any(feature = "extras-otel", feature = "otel"))] + pub mod otel { + pub use rustapi_extras::otel; + } + + #[cfg(any(feature = "extras-structured-logging", feature = "structured-logging"))] + pub mod structured_logging { + pub use rustapi_extras::structured_logging; + } + + #[cfg(any(feature = "extras-replay", feature = "replay"))] + pub mod replay { + pub use rustapi_extras::replay; + } +} + +// Legacy root module aliases. +#[cfg(any(feature = "protocol-toon", feature = "toon"))] +#[deprecated(note = "Use rustapi_rs::protocol::toon instead")] pub mod toon { - //! TOON (Token-Oriented Object Notation) support - //! - //! TOON is a compact format for LLM communication that reduces token usage by 20-40%. - //! - //! # Example - //! - //! ```rust,ignore - //! use rustapi_rs::toon::{Toon, Negotiate, AcceptHeader}; - //! - //! // As extractor - //! async fn handler(Toon(data): Toon) -> impl IntoResponse { ... } - //! - //! // As response - //! async fn handler() -> Toon { Toon(my_data) } - //! - //! // Content negotiation (returns JSON or TOON based on Accept header) - //! async fn handler(accept: AcceptHeader) -> Negotiate { - //! Negotiate::new(my_data, accept.preferred) - //! } - //! ``` - pub use rustapi_toon::*; + pub use crate::protocol::toon::*; } -// Re-export WebSocket support (feature-gated) -#[cfg(feature = "ws")] +#[cfg(any(feature = "protocol-ws", feature = "ws"))] +#[deprecated(note = "Use rustapi_rs::protocol::ws instead")] pub mod ws { - //! WebSocket support for real-time bidirectional communication - //! - //! This module provides WebSocket functionality through the `WebSocket` extractor, - //! enabling real-time communication patterns like chat, live updates, and streaming. - //! - //! # Example - //! - //! ```rust,ignore - //! use rustapi_rs::ws::{WebSocket, Message}; - //! - //! async fn websocket_handler(ws: WebSocket) -> impl IntoResponse { - //! ws.on_upgrade(|mut socket| async move { - //! while let Some(Ok(msg)) = socket.recv().await { - //! if let Message::Text(text) = msg { - //! socket.send(Message::Text(format!("Echo: {}", text))).await.ok(); - //! } - //! } - //! }) - //! } - //! ``` - pub use rustapi_ws::*; + pub use crate::protocol::ws::*; } -// Re-export View/Template support (feature-gated) -#[cfg(feature = "view")] +#[cfg(any(feature = "protocol-view", feature = "view"))] +#[deprecated(note = "Use rustapi_rs::protocol::view instead")] pub mod view { - //! Template engine support for server-side rendering - //! - //! This module provides Tera-based templating with the `View` response type, - //! enabling server-side HTML rendering with template inheritance and context. - //! - //! # Example - //! - //! ```rust,ignore - //! use rustapi_rs::view::{Templates, View, ContextBuilder}; - //! - //! #[derive(Clone)] - //! struct AppState { - //! templates: Templates, - //! } - //! - //! async fn index(State(state): State) -> View<()> { - //! View::new(&state.templates, "index.html") - //! .with("title", "Home") - //! .with("message", "Welcome!") - //! } - //! ``` - pub use rustapi_view::*; + pub use crate::protocol::view::*; } -// Re-export gRPC support (feature-gated) -#[cfg(feature = "grpc")] +#[cfg(any(feature = "protocol-grpc", feature = "grpc"))] +#[deprecated(note = "Use rustapi_rs::protocol::grpc instead")] pub mod grpc { - //! gRPC integration helpers powered by Tonic. - //! - //! Use this module to run RustAPI HTTP and gRPC servers side-by-side. - pub use rustapi_grpc::*; + pub use crate::protocol::grpc::*; } -/// Prelude module - import everything you need with `use rustapi_rs::prelude::*` +// Legacy root extras re-exports for compatibility. +#[cfg(any(feature = "extras-jwt", feature = "jwt"))] +pub use rustapi_extras::jwt; +#[cfg(any(feature = "extras-jwt", feature = "jwt"))] +pub use rustapi_extras::{ + create_token, AuthUser, JwtError, JwtLayer, JwtValidation, ValidatedClaims, +}; + +#[cfg(any(feature = "extras-cors", feature = "cors"))] +pub use rustapi_extras::cors; +#[cfg(any(feature = "extras-cors", feature = "cors"))] +pub use rustapi_extras::{AllowedOrigins, CorsLayer}; + +#[cfg(any(feature = "extras-rate-limit", feature = "rate-limit"))] +pub use rustapi_extras::rate_limit; +#[cfg(any(feature = "extras-rate-limit", feature = "rate-limit"))] +pub use rustapi_extras::RateLimitLayer; + +#[cfg(any(feature = "extras-config", feature = "config"))] +pub use rustapi_extras::config; +#[cfg(any(feature = "extras-config", feature = "config"))] +pub use rustapi_extras::{ + env_or, env_parse, load_dotenv, load_dotenv_from, require_env, Config, ConfigError, + Environment as ExtrasEnvironment, +}; + +#[cfg(any(feature = "extras-sqlx", feature = "sqlx"))] +pub use rustapi_extras::{convert_sqlx_error, SqlxErrorExt}; + +#[cfg(any(feature = "extras-api-key", feature = "api-key"))] +pub use rustapi_extras::api_key; +#[cfg(any(feature = "extras-cache", feature = "cache"))] +pub use rustapi_extras::cache; +#[cfg(any(feature = "extras-circuit-breaker", feature = "circuit-breaker"))] +pub use rustapi_extras::circuit_breaker; +#[cfg(any(feature = "extras-dedup", feature = "dedup"))] +pub use rustapi_extras::dedup; +#[cfg(any(feature = "extras-guard", feature = "guard"))] +pub use rustapi_extras::guard; +#[cfg(any(feature = "extras-logging", feature = "logging"))] +pub use rustapi_extras::logging; +#[cfg(any(feature = "extras-otel", feature = "otel"))] +pub use rustapi_extras::otel; +#[cfg(any(feature = "extras-replay", feature = "replay"))] +pub use rustapi_extras::replay; +#[cfg(any(feature = "extras-retry", feature = "retry"))] +pub use rustapi_extras::retry; +#[cfg(any(feature = "extras-sanitization", feature = "sanitization"))] +pub use rustapi_extras::sanitization; +#[cfg(any(feature = "extras-security-headers", feature = "security-headers"))] +pub use rustapi_extras::security_headers; +#[cfg(any(feature = "extras-structured-logging", feature = "structured-logging"))] +pub use rustapi_extras::structured_logging; +#[cfg(any(feature = "extras-timeout", feature = "timeout"))] +pub use rustapi_extras::timeout; + +/// Prelude module: `use rustapi_rs::prelude::*`. pub mod prelude { - // Core types - pub use rustapi_core::validation::Validatable; - pub use rustapi_core::{ - delete, - delete_route, - get, - get_route, - patch, - patch_route, - post, - post_route, - put, - put_route, - serve_dir, - sse_response, - // Error handling - ApiError, - AsyncValidatedJson, - Body, - ClientIp, - Created, - Extension, - HeaderValue, - Headers, - Html, - // Response types - IntoResponse, - // Extractors - Json, - KeepAlive, - // Multipart - Multipart, - MultipartConfig, - MultipartField, - NoContent, - Path, - Query, - Redirect, - // Request context - Request, - // Middleware - RequestId, - RequestIdLayer, - Response, - Result, - // Route type for macro-based routing - Route, - // Router - Router, - // App builder - RustApi, - RustApiConfig, - // Streaming responses - Sse, - SseEvent, - State, - // Static files - StaticFile, - StaticFileConfig, - StatusCode, - StreamBody, - TracingLayer, - Typed, - TypedPath, - UploadedFile, - ValidatedJson, - WithStatus, + pub use crate::core::Validatable; + pub use crate::core::{ + delete, delete_route, get, get_route, patch, patch_route, post, post_route, put, put_route, + route, serve_dir, sse_response, ApiError, AsyncValidatedJson, Body, BodyLimitLayer, + ClientIp, Created, Extension, HeaderValue, Headers, Html, IntoResponse, Json, KeepAlive, + Multipart, MultipartConfig, MultipartField, NoContent, Path, Query, Redirect, Request, + RequestId, RequestIdLayer, Response, Result, Route, Router, RustApi, RustApiConfig, Sse, + SseEvent, State, StaticFile, StaticFileConfig, StatusCode, StreamBody, TracingLayer, Typed, + TypedPath, UploadedFile, ValidatedJson, WithStatus, }; - // Compression middleware (feature-gated in core) - #[cfg(feature = "compression")] - pub use rustapi_core::middleware::{CompressionAlgorithm, CompressionConfig}; - #[cfg(feature = "compression")] - pub use rustapi_core::CompressionLayer; + #[cfg(any(feature = "core-compression", feature = "compression"))] + pub use crate::core::{CompressionAlgorithm, CompressionConfig, CompressionLayer}; - // Cookies extractor (feature-gated in core) - #[cfg(feature = "cookies")] - pub use rustapi_core::Cookies; - - // Re-export the route! macro - pub use rustapi_core::route; + #[cfg(any(feature = "core-cookies", feature = "cookies"))] + pub use crate::core::Cookies; - // Re-export TypedPath derive macro pub use rustapi_macros::ApiError; + pub use rustapi_macros::Schema; pub use rustapi_macros::TypedPath; - // Re-export validation - use validator derive macro directly pub use rustapi_validate::v2::AsyncValidate; pub use rustapi_validate::v2::Validate as V2Validate; - #[cfg(feature = "legacy-validator")] - pub use validator::Validate; - - // Re-export OpenAPI schema derive - pub use rustapi_openapi::Schema; - // Re-export crates needed by Schema derive macro - // These are required for the macro-generated code to compile - pub use rustapi_openapi; - pub use serde_json; + #[cfg(any(feature = "core-legacy-validator", feature = "legacy-validator"))] + pub use validator::Validate; - // Re-export commonly used external types pub use serde::{Deserialize, Serialize}; pub use tracing::{debug, error, info, trace, warn}; - // JWT types (feature-gated) - #[cfg(feature = "jwt")] - pub use rustapi_extras::{ - create_token, AuthUser, JwtError, JwtLayer, JwtValidation, ValidatedClaims, - }; + #[cfg(any(feature = "extras-jwt", feature = "jwt"))] + pub use crate::{create_token, AuthUser, JwtError, JwtLayer, JwtValidation, ValidatedClaims}; - // CORS types (feature-gated) - #[cfg(feature = "cors")] - pub use rustapi_extras::{AllowedOrigins, CorsLayer}; + #[cfg(any(feature = "extras-cors", feature = "cors"))] + pub use crate::{AllowedOrigins, CorsLayer}; - // Rate limiting types (feature-gated) - #[cfg(feature = "rate-limit")] - pub use rustapi_extras::RateLimitLayer; + #[cfg(any(feature = "extras-rate-limit", feature = "rate-limit"))] + pub use crate::RateLimitLayer; - // Configuration types (feature-gated) - #[cfg(feature = "config")] - pub use rustapi_extras::{ + #[cfg(any(feature = "extras-config", feature = "config"))] + pub use crate::{ env_or, env_parse, load_dotenv, load_dotenv_from, require_env, Config, ConfigError, - Environment, + ExtrasEnvironment, }; - // SQLx types (feature-gated) - #[cfg(feature = "sqlx")] - pub use rustapi_extras::{convert_sqlx_error, SqlxErrorExt}; + #[cfg(any(feature = "extras-sqlx", feature = "sqlx"))] + pub use crate::{convert_sqlx_error, SqlxErrorExt}; - // TOON types (feature-gated) - #[cfg(feature = "toon")] - pub use rustapi_toon::{AcceptHeader, LlmResponse, Negotiate, OutputFormat, Toon}; + #[cfg(any(feature = "protocol-toon", feature = "toon"))] + pub use crate::protocol::toon::{AcceptHeader, LlmResponse, Negotiate, OutputFormat, Toon}; - // WebSocket types (feature-gated) - #[cfg(feature = "ws")] - pub use rustapi_ws::{Broadcast, Message, WebSocket, WebSocketStream}; + #[cfg(any(feature = "protocol-ws", feature = "ws"))] + pub use crate::protocol::ws::{Broadcast, Message, WebSocket, WebSocketStream}; - // View/Template types (feature-gated) - #[cfg(feature = "view")] - pub use rustapi_view::{ContextBuilder, Templates, TemplatesConfig, View}; + #[cfg(any(feature = "protocol-view", feature = "view"))] + pub use crate::protocol::view::{ContextBuilder, Templates, TemplatesConfig, View}; - // gRPC integration (feature-gated) - #[cfg(feature = "grpc")] - pub use rustapi_grpc::{ + #[cfg(any(feature = "protocol-grpc", feature = "grpc"))] + pub use crate::protocol::grpc::{ run_concurrently, run_rustapi_and_grpc, run_rustapi_and_grpc_with_shutdown, }; } @@ -391,7 +339,6 @@ mod tests { #[test] fn prelude_imports_work() { - // This test ensures prelude exports compile correctly let _: fn() -> Result<()> = || Ok(()); } } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index db584b20..392ff27e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -67,10 +67,16 @@ use rustapi_rs::prelude::*; Responsibilities: - Re-export all public types from internal crates -- Manage feature flags (`jwt`, `cors`, `toon`, etc.) +- Manage feature flags (`core-*`, `protocol-*`, `extras-*`) - Provide version stability guarantees - Documentation entry point +Facade module layout: +- `rustapi_rs::core` - Stable routing/handler/extractor/response/middleware surface. +- `rustapi_rs::protocol` - Optional protocol modules (`toon`, `ws`, `view`, `grpc`, `http3`). +- `rustapi_rs::extras` - Optional production middleware/integration modules. +- `rustapi_rs::__private` - Macro/runtime wiring (not part of public contract). + ```rust // rustapi-rs/src/lib.rs (simplified) pub mod prelude { @@ -83,17 +89,17 @@ pub mod prelude { pub use rustapi_openapi::Schema; pub use rustapi_validate::Validate; - #[cfg(feature = "toon")] - pub use rustapi_toon::{Toon, LlmResponse, AcceptHeader}; + #[cfg(feature = "protocol-toon")] + pub use crate::protocol::toon::{Toon, LlmResponse, AcceptHeader}; - #[cfg(feature = "jwt")] - pub use rustapi_extras::jwt::*; + #[cfg(feature = "extras-jwt")] + pub use crate::extras::jwt::*; - #[cfg(feature = "ws")] - pub use rustapi_ws::{WebSocket, WebSocketUpgrade, WebSocketStream, Message, Broadcast}; + #[cfg(feature = "protocol-ws")] + pub use crate::protocol::ws::{WebSocket, WebSocketUpgrade, WebSocketStream, Message, Broadcast}; - #[cfg(feature = "view")] - pub use rustapi_view::{Templates, View, ContextBuilder}; + #[cfg(feature = "protocol-view")] + pub use crate::protocol::view::{Templates, View, ContextBuilder}; } ``` @@ -168,7 +174,7 @@ struct User { Features: - Native v2 validation engine is the default (`rustapi_macros::Validate`) - `ValidatedJson` and `AsyncValidatedJson` extractors -- Legacy `validator` compatibility is optional via `legacy-validator` +- Legacy `validator` compatibility is optional via `core-legacy-validator` - Automatic 422 responses with field errors - Custom validation rules @@ -213,12 +219,12 @@ Headers provided by `LlmResponse`: | Component | Feature Flag | Purpose | |-----------|--------------|---------| -| JWT Auth | `jwt` | `AuthUser` extractor, `JwtLayer` | -| CORS | `cors` | `CorsLayer` with builder | -| Rate Limit | `rate-limit` | IP-based throttling | +| JWT Auth | `extras-jwt` | `AuthUser` extractor, `JwtLayer` | +| CORS | `extras-cors` | `CorsLayer` with builder | +| Rate Limit | `extras-rate-limit` | IP-based throttling | | Body Limit | default | Max request body size | | Request ID | default | Unique request tracking | -| **Audit Logging** | `audit` | GDPR/SOC2 compliance logging | +| **Replay** | `extras-replay` | Request capture + replay diagnostics | | **Circuit Breaker** | default | Fault tolerance patterns | | **Retry** | default | Automatic retry with backoff | @@ -529,7 +535,7 @@ async fn test_with_mock_db() { | Area | Technique | Expected Gain | |------|-----------|---------------| -| JSON Parsing | `simd-json` feature flag | 2-4x faster | +| JSON Parsing | `core-simd-json` feature flag | 2-4x faster | | Path Params | `SmallVec<[_; 4]>` | Stack-optimized, fewer allocations | | Tracing | Conditional compilation | 10-20% less overhead | | String Handling | Path borrowing | Fewer copies | @@ -664,14 +670,15 @@ The facade pattern is the key: `rustapi-rs` provides a stable surface, while int - `cargo-rustapi`: CLI tool. - **Internal/Support Crates**: - `rustapi-core`, `rustapi-macros`, `rustapi-validate`; - - `rustapi-openapi`, `rustapi-extras`, `rustapi-toon`; + - `rustapi-openapi`, `rustapi-extras`, `rustapi-toon`, `rustapi-grpc`; - `rustapi-ws`, `rustapi-view`, `rustapi-testing`, `rustapi-jobs`. ### Semver Policy -- **Current Status**: 0.x (Unstable). -- **Policy**: Public API changes may occur. `rustapi-rs` versions will follow SemVer, but internal crate versions (`rustapi-core` etc.) are synchronized but treated as implementation details. +- **Facade Contract**: `rustapi-rs` is the compatibility boundary and follows strict SemVer rules. +- **Internal Crates**: implementation details; APIs may change without facade guarantees. +- **Source of truth**: see `CONTRACT.md` for SemVer/MSRV/deprecation policy. ### Workspace Members -11 Library Crates + 2 Bench suites + 1 CLI (`crates/cargo-rustapi`). +12 Library Crates + 2 Bench suites + 1 CLI (`crates/cargo-rustapi`). diff --git a/docs/FEATURES.md b/docs/FEATURES.md index c0375ca3..2a10d112 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -180,7 +180,7 @@ Checks `X-Forwarded-For`, `X-Real-IP`, then socket address. ### `AuthUser` (JWT) -Extract authenticated user claims (requires `jwt` feature). +Extract authenticated user claims (requires `extras-jwt` feature). ```rust use rustapi_rs::extract::AuthUser; @@ -627,7 +627,7 @@ Token-Oriented Object Notation for LLM optimization. ### Basic Usage ```rust -use rustapi_rs::toon::Toon; +use rustapi_rs::protocol::toon::Toon; #[rustapi_rs::get("/ai/users")] async fn ai_users() -> Toon { @@ -638,7 +638,7 @@ async fn ai_users() -> Toon { ### Content Negotiation ```rust -use rustapi_rs::toon::{LlmResponse, AcceptHeader}; +use rustapi_rs::protocol::toon::{LlmResponse, AcceptHeader}; #[rustapi_rs::get("/users")] async fn users(accept: AcceptHeader) -> LlmResponse { @@ -685,12 +685,12 @@ users[(id:1,name:Alice,email:alice@example.com)(id:2,name:Bob,email:bob@example. ## WebSocket -Real-time bidirectional communication support (requires `ws` feature). +Real-time bidirectional communication support (requires `protocol-ws` feature). ### Basic WebSocket Handler ```rust -use rustapi_rs::ws::{WebSocket, WebSocketUpgrade, WebSocketStream, Message}; +use rustapi_rs::protocol::ws::{WebSocket, WebSocketUpgrade, WebSocketStream, Message}; #[rustapi_rs::get("/ws")] async fn websocket(ws: WebSocket) -> WebSocketUpgrade { @@ -733,7 +733,7 @@ async fn handle_connection(mut stream: WebSocketStream) { For pub/sub patterns (chat rooms, live updates): ```rust -use rustapi_rs::ws::{Broadcast, Message}; +use rustapi_rs::protocol::ws::{Broadcast, Message}; use std::sync::Arc; #[tokio::main] @@ -810,12 +810,12 @@ async fn websocket( ## Template Engine -Server-side HTML rendering with Tera templates (requires `view` feature). +Server-side HTML rendering with Tera templates (requires `protocol-view` feature). ### Setup ```rust -use rustapi_rs::view::{Templates, TemplatesConfig}; +use rustapi_rs::protocol::view::{Templates, TemplatesConfig}; #[tokio::main] async fn main() { @@ -835,7 +835,7 @@ async fn main() { ### Basic Template Rendering ```rust -use rustapi_rs::view::{Templates, View}; +use rustapi_rs::protocol::view::{Templates, View}; #[rustapi_rs::get("/")] async fn home(templates: Templates) -> View<()> { @@ -864,7 +864,7 @@ async fn user_page( ### Template with Extra Context ```rust -use rustapi_rs::view::{Templates, View, ContextBuilder}; +use rustapi_rs::protocol::view::{Templates, View, ContextBuilder}; #[rustapi_rs::get("/dashboard")] async fn dashboard(templates: Templates) -> View { @@ -1162,17 +1162,20 @@ rustapi-rs = { version = "0.1.335", features = ["full"] } | Feature | Description | |---------|-------------| -| `swagger-ui` | Swagger UI (default) | -| `jwt` | JWT authentication | -| `cors` | CORS middleware | -| `rate-limit` | Rate limiting | -| `toon` | TOON format | -| `cookies` | Cookie extraction | -| `ws` | WebSocket support | -| `view` | Template engine (Tera) | -| `simd-json` | 2-4x faster JSON parsing | -| `audit` | GDPR/SOC2 audit logging | -| `full` | All features | +| `core` | Default stable core (`core-openapi`, `core-tracing`) | +| `core-openapi` | OpenAPI + docs endpoint support | +| `core-tracing` | Tracing middleware and instrumentation | +| `core-simd-json` | 2-4x faster JSON parsing | +| `core-cookies` | Cookie extraction | +| `protocol-toon` | TOON format | +| `protocol-ws` | WebSocket support | +| `protocol-view` | Template engine (Tera) | +| `protocol-grpc` | gRPC bridge helpers | +| `extras-jwt` | JWT authentication | +| `extras-cors` | CORS middleware | +| `extras-rate-limit` | Rate limiting | +| `extras-replay` | Request replay tooling | +| `full` | `core + protocol-all + extras-all` | --- @@ -1282,10 +1285,10 @@ let events = store.query() ## Performance Tips -### 1. Use `simd-json` (when available) +### 1. Use `core-simd-json` (when available) ```toml -rustapi-rs = { version = "0.1.335", features = ["simd-json"] } +rustapi-rs = { version = "0.1.335", features = ["core-simd-json"] } ``` 2-4x faster JSON parsing. diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 777e772a..6f51d674 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -6,7 +6,7 @@ ## Prerequisites -- Rust 1.75 or later +- Rust 1.78 or later (MSRV) - Cargo (comes with Rust) ```bash @@ -36,23 +36,25 @@ Or with specific features: ```toml [dependencies] -rustapi-rs = { version = "0.1.335", features = ["jwt", "cors", "toon", "ws", "view"] } +rustapi-rs = { version = "0.1.335", features = ["extras-jwt", "extras-cors", "protocol-toon", "protocol-ws", "protocol-view"] } ``` ### Available Features | Feature | Description | |---------|-------------| -| `swagger-ui` | Swagger UI at `/docs` (enabled by default) | -| `jwt` | JWT authentication | -| `cors` | CORS middleware | -| `rate-limit` | IP-based rate limiting | -| `toon` | LLM-optimized TOON format | -| `ws` | WebSocket support | -| `view` | Template engine (Tera) | -| `simd-json` | 2-4x faster JSON parsing | -| `audit` | GDPR/SOC2 audit logging | -| `full` | All features | +| `core` | Default stable core (`core-openapi`, `core-tracing`) | +| `core-openapi` | OpenAPI + docs endpoint support | +| `core-tracing` | Tracing middleware and instrumentation | +| `protocol-toon` | LLM-optimized TOON format | +| `protocol-ws` | WebSocket support | +| `protocol-view` | Template engine (Tera) | +| `protocol-grpc` | gRPC bridge helpers | +| `extras-jwt` | JWT authentication | +| `extras-cors` | CORS middleware | +| `extras-rate-limit` | IP-based rate limiting | +| `extras-config` | Environment/config helpers | +| `full` | `core + protocol-all + extras-all` | --- @@ -368,11 +370,11 @@ ApiError::internal("message") // 500 ### CORS ```toml -rustapi-rs = { version = "0.1.335", features = ["cors"] } +rustapi-rs = { version = "0.1.335", features = ["extras-cors"] } ``` ```rust -use rustapi_rs::middleware::CorsLayer; +use rustapi_rs::extras::cors::CorsLayer; RustApi::new() .layer(CorsLayer::permissive()) // Allow all origins @@ -389,12 +391,11 @@ RustApi::new() ### JWT Authentication ```toml -rustapi-rs = { version = "0.1.335", features = ["jwt"] } +rustapi-rs = { version = "0.1.335", features = ["extras-jwt"] } ``` ```rust -use rustapi_rs::middleware::JwtLayer; -use rustapi_rs::extract::AuthUser; +use rustapi_rs::extras::jwt::{AuthUser, JwtLayer}; #[derive(Serialize, Deserialize)] struct Claims { @@ -411,7 +412,7 @@ RustApi::new() async fn protected(user: AuthUser) -> Json { Json(Response { - message: format!("Hello, {}", user.claims.sub) + message: format!("Hello, {}", user.0.sub) }) } ``` @@ -419,11 +420,11 @@ async fn protected(user: AuthUser) -> Json { ### Rate Limiting ```toml -rustapi-rs = { version = "0.1.335", features = ["rate-limit"] } +rustapi-rs = { version = "0.1.335", features = ["extras-rate-limit"] } ``` ```rust -use rustapi_rs::middleware::RateLimitLayer; +use rustapi_rs::extras::rate_limit::RateLimitLayer; RustApi::new() .layer(RateLimitLayer::new(100, Duration::from_secs(60))) // 100 req/min @@ -437,11 +438,11 @@ RustApi::new() ## TOON Format (LLM Optimization) ```toml -rustapi-rs = { version = "0.1.335", features = ["toon"] } +rustapi-rs = { version = "0.1.335", features = ["protocol-toon"] } ``` ```rust -use rustapi_rs::toon::{Toon, LlmResponse, AcceptHeader}; +use rustapi_rs::protocol::toon::{Toon, LlmResponse, AcceptHeader}; // Direct TOON response #[rustapi_rs::get("/ai/users")] @@ -468,11 +469,11 @@ Response includes token counting headers: Real-time bidirectional communication: ```toml -rustapi-rs = { version = "0.1.335", features = ["ws"] } +rustapi-rs = { version = "0.1.335", features = ["protocol-ws"] } ``` ```rust -use rustapi_rs::ws::{WebSocket, WebSocketUpgrade, WebSocketStream, Message}; +use rustapi_rs::protocol::ws::{WebSocket, WebSocketUpgrade, WebSocketStream, Message}; #[rustapi_rs::get("/ws")] async fn websocket(ws: WebSocket) -> WebSocketUpgrade { @@ -505,7 +506,7 @@ websocat ws://localhost:8080/ws Server-side HTML rendering with Tera: ```toml -rustapi-rs = { version = "0.1.335", features = ["view"] } +rustapi-rs = { version = "0.1.335", features = ["protocol-view"] } ``` Create a template file `templates/index.html`: @@ -521,7 +522,7 @@ Create a template file `templates/index.html`: Use in your handler: ```rust -use rustapi_rs::view::{Templates, View, TemplatesConfig}; +use rustapi_rs::protocol::view::{Templates, View, TemplatesConfig}; #[tokio::main] async fn main() { @@ -572,7 +573,7 @@ cargo rustapi new my-api --interactive cargo rustapi watch # Add features or dependencies -cargo rustapi add cors jwt +cargo rustapi add extras-cors extras-jwt # Check environment health cargo rustapi doctor @@ -704,10 +705,10 @@ struct AnyBody { ... } ### Swagger UI not showing -Check that the `swagger-ui` feature is enabled (it's on by default): +Check that `core-openapi` is enabled (it is included in the default `core` feature): ```toml -rustapi-rs = { version = "0.1.335", features = ["swagger-ui"] } +rustapi-rs = { version = "0.1.335", features = ["core-openapi"] } ``` ### CLI Commands Not Working diff --git a/docs/README.md b/docs/README.md index a90f56bf..ab5a2da6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,6 +27,12 @@ RustAPI is an ergonomic web framework for Rust, inspired by FastAPI's developer > *"API surface is ours, engines can change."* RustAPI provides a stable, ergonomic public API. Internal dependencies (`hyper`, `tokio`, `validator`) are implementation details that can be upgraded without breaking your code. +The stable contract lives in `rustapi-rs`; internal crates are not compatibility targets. + +Feature taxonomy: +- `core-*` for framework core behavior. +- `protocol-*` for optional protocol integrations. +- `extras-*` for optional production middleware/integrations. ## Getting Started From 96853f7411ddebcddbbed0ebdbe1814a4846c99d Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 11:31:53 +0300 Subject: [PATCH 02/10] Add branch-protection doc; update API, code & docs Add .github/BRANCH_PROTECTION.md with UI and API instructions for protecting main and requiring Public API checks. Update CHANGELOG and CONTRACT to document facade-first CORE stabilization, public API governance (snapshots/CI), feature taxonomy and deprecation/migration timeline. Silence unused-symbol warnings by adding #[allow(dead_code)] to several rustapi-core helpers and path validation types/functions, and conditionally gate sqlx-related imports in rustapi-extras with feature cfgs. Refresh docs and examples to the new feature naming scheme (extras-*, protocol-*, core-*) and update performance wording to reflect the renamed simd-json feature. --- .github/BRANCH_PROTECTION.md | 54 ++++++++++++++++++++++ CHANGELOG.md | 23 +++++++++ CONTRACT.md | 6 +++ crates/rustapi-core/src/auto_route.rs | 1 + crates/rustapi-core/src/auto_schema.rs | 1 + crates/rustapi-core/src/json.rs | 5 ++ crates/rustapi-core/src/path_validation.rs | 3 ++ crates/rustapi-extras/src/sqlx/mod.rs | 10 ++++ docs/PHILOSOPHY.md | 14 +++--- docs/cookbook/src/concepts/performance.md | 4 +- docs/cookbook/src/learning/README.md | 10 ++-- docs/cookbook/src/recipes/jwt_auth.md | 6 +-- 12 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 .github/BRANCH_PROTECTION.md diff --git a/.github/BRANCH_PROTECTION.md b/.github/BRANCH_PROTECTION.md new file mode 100644 index 00000000..5b44014c --- /dev/null +++ b/.github/BRANCH_PROTECTION.md @@ -0,0 +1,54 @@ +# Branch Protection Setup + +Configure branch protection for `main` so public API checks are required. + +## Required Status Checks + +Add these checks as required: + +- `Public API / Snapshot Drift` +- `Public API / Label Gate` + +## GitHub UI Path + +1. Repository `Settings` +2. `Branches` +3. Edit rule for `main` +4. Enable `Require status checks to pass before merging` +5. Add the two checks above + +## API Automation (optional) + +Use a fine-grained token with `Administration: Read and write` for the repository. + +```powershell +$owner = "Tuntii" +$repo = "RustAPI" +$token = $env:GITHUB_TOKEN + +$body = @{ + required_status_checks = @{ + strict = $true + contexts = @( + "Public API / Snapshot Drift", + "Public API / Label Gate" + ) + } + enforce_admins = $true + required_pull_request_reviews = @{ + required_approving_review_count = 1 + } + restrictions = $null +} | ConvertTo-Json -Depth 10 + +Invoke-RestMethod ` + -Method Put ` + -Uri "https://api.github.com/repos/$owner/$repo/branches/main/protection" ` + -Headers @{ + Authorization = "Bearer $token" + Accept = "application/vnd.github+json" + "X-GitHub-Api-Version" = "2022-11-28" + } ` + -Body $body ` + -ContentType "application/json" +``` diff --git a/CHANGELOG.md b/CHANGELOG.md index e526e3a6..95c249a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- **Facade-first CORE stabilization**: + - `rustapi-rs` public surface is now explicitly curated (`core`, `protocol`, `extras`, `prelude`). + - Internal wiring moved behind `rustapi_rs::__private` for macro/runtime integration. + - `rustapi-core` internal modules tightened (`pub(crate)`/private where applicable). + - `Handler` trait sealed to prevent external implementation leakage. +- **Feature taxonomy refresh**: + - Canonical naming is now `core-*`, `protocol-*`, `extras-*`. + - Meta features standardized: `core`, `protocol-all`, `extras-all`, `full`. + - Legacy feature names remain as compatibility aliases and are deprecated. + +### Added +- **Public API governance**: + - Snapshot files under `api/public/` for `rustapi-rs` (default + all-features). + - New CI workflow `.github/workflows/public-api.yml`: + - snapshot drift check + - PR label gate requiring `breaking` or `feature` when snapshot changes. +- **Compatibility contract**: + - New `CONTRACT.md` defining SemVer, MSRV (1.78), deprecation and feature policies. + +### Deprecated +- Legacy facade paths and feature aliases are soft-deprecated and scheduled for removal no earlier than two minor releases after announcement. + ## [0.1.300] - 2026-02-06 ### Added diff --git a/CONTRACT.md b/CONTRACT.md index 2b028be0..d151d0be 100644 --- a/CONTRACT.md +++ b/CONTRACT.md @@ -41,6 +41,8 @@ Do not depend on internal crate APIs for long-term compatibility. - explicit migration path in docs/release notes - Minimum deprecation window before removal: 2 minor releases. - Removals occur only in major releases. +- Current compatibility window for legacy aliases introduced in this cycle: + - First eligible removal: `v0.3.0` (assuming deprecation introduced in `v0.1.x`). ## 5. Feature Flag Policy @@ -54,6 +56,10 @@ Do not depend on internal crate APIs for long-term compatibility. - `extras-all` - `full` - Legacy aliases may exist temporarily for migration but must be treated as deprecated and eventually removed on a published timeline. +- Published timeline for this migration set: + - `v0.1.x`: aliases available, deprecation warnings/documentation. + - `v0.2.x`: aliases still available, migration reminders. + - `v0.3.0+`: aliases may be removed. ## 6. Internal Leakage Rule diff --git a/crates/rustapi-core/src/auto_route.rs b/crates/rustapi-core/src/auto_route.rs index 8fd8b569..a0b8feba 100644 --- a/crates/rustapi-core/src/auto_route.rs +++ b/crates/rustapi-core/src/auto_route.rs @@ -67,6 +67,7 @@ pub fn collect_auto_routes() -> Vec { /// Get the count of auto-registered routes without collecting them. /// /// Useful for debugging and logging. +#[allow(dead_code)] pub fn auto_route_count() -> usize { AUTO_ROUTES.len() } diff --git a/crates/rustapi-core/src/auto_schema.rs b/crates/rustapi-core/src/auto_schema.rs index 22183318..b9eabc1d 100644 --- a/crates/rustapi-core/src/auto_schema.rs +++ b/crates/rustapi-core/src/auto_schema.rs @@ -21,6 +21,7 @@ pub fn apply_auto_schemas(spec: &mut rustapi_openapi::OpenApiSpec) { } /// Get the count of auto-registered schema registration functions. +#[allow(dead_code)] pub fn auto_schema_count() -> usize { AUTO_SCHEMAS.len() } diff --git a/crates/rustapi-core/src/json.rs b/crates/rustapi-core/src/json.rs index 7c809a15..49ad62c9 100644 --- a/crates/rustapi-core/src/json.rs +++ b/crates/rustapi-core/src/json.rs @@ -49,6 +49,7 @@ pub fn from_slice(slice: &[u8]) -> Result { /// This variant allows simd-json to parse in-place without copying, /// providing maximum performance. #[cfg(feature = "simd-json")] +#[allow(dead_code)] pub fn from_slice_mut(slice: &mut [u8]) -> Result { simd_json::from_slice(slice).map_err(JsonError::SimdJson) } @@ -57,6 +58,7 @@ pub fn from_slice_mut(slice: &mut [u8]) -> Result(slice: &mut [u8]) -> Result { serde_json::from_slice(slice).map_err(JsonError::SerdeJson) } @@ -65,6 +67,7 @@ pub fn from_slice_mut(slice: &mut [u8]) -> Result(value: &T) -> Result, JsonError> { simd_json::to_vec(value).map_err(JsonError::SimdJson) } @@ -73,6 +76,7 @@ pub fn to_vec(value: &T) -> Result, JsonError> { /// /// Uses pre-allocated buffer with estimated capacity for better performance. #[cfg(not(feature = "simd-json"))] +#[allow(dead_code)] pub fn to_vec(value: &T) -> Result, JsonError> { serde_json::to_vec(value).map_err(JsonError::SerdeJson) } @@ -106,6 +110,7 @@ pub fn to_vec_with_capacity( } /// Serialize a value to a pretty-printed JSON byte vector. +#[allow(dead_code)] pub fn to_vec_pretty(value: &T) -> Result, JsonError> { serde_json::to_vec_pretty(value).map_err(JsonError::SerdeJson) } diff --git a/crates/rustapi-core/src/path_validation.rs b/crates/rustapi-core/src/path_validation.rs index 3f2d9afa..14dd4ca7 100644 --- a/crates/rustapi-core/src/path_validation.rs +++ b/crates/rustapi-core/src/path_validation.rs @@ -6,6 +6,7 @@ /// Result of path validation #[derive(Debug, Clone, PartialEq, Eq)] +#[allow(dead_code)] pub enum PathValidationError { /// Path must start with '/' MustStartWithSlash { path: String }, @@ -151,6 +152,7 @@ impl std::error::Error for PathValidationError {} /// assert!(validate_path("/users/{}").is_err()); // Empty parameter /// assert!(validate_path("/users/{123}").is_err()); // Parameter starts with digit /// ``` +#[allow(dead_code)] pub fn validate_path(path: &str) -> Result<(), PathValidationError> { // Path must start with / if !path.starts_with('/') { @@ -250,6 +252,7 @@ pub fn validate_path(path: &str) -> Result<(), PathValidationError> { } /// Check if a path is valid (convenience function) +#[allow(dead_code)] pub fn is_valid_path(path: &str) -> bool { validate_path(path).is_ok() } diff --git a/crates/rustapi-extras/src/sqlx/mod.rs b/crates/rustapi-extras/src/sqlx/mod.rs index eeb6c0f2..3f3305c1 100644 --- a/crates/rustapi-extras/src/sqlx/mod.rs +++ b/crates/rustapi-extras/src/sqlx/mod.rs @@ -48,8 +48,18 @@ //! } //! ``` +#[cfg(any( + feature = "sqlx-postgres", + feature = "sqlx-mysql", + feature = "sqlx-sqlite" +))] use rustapi_core::health::{HealthCheck, HealthCheckBuilder, HealthStatus}; use rustapi_core::ApiError; +#[cfg(any( + feature = "sqlx-postgres", + feature = "sqlx-mysql", + feature = "sqlx-sqlite" +))] use std::sync::Arc; use std::time::Duration; use thiserror::Error; diff --git a/docs/PHILOSOPHY.md b/docs/PHILOSOPHY.md index 13284a2a..56e2bc85 100644 --- a/docs/PHILOSOPHY.md +++ b/docs/PHILOSOPHY.md @@ -117,16 +117,16 @@ rustapi-rs = "0.1.335" rustapi-rs = { version = "0.1.335", features = ["full"] } # Pick what you need -rustapi-rs = { version = "0.1.335", features = ["jwt", "cors", "toon"] } +rustapi-rs = { version = "0.1.335", features = ["extras-jwt", "extras-cors", "protocol-toon"] } ``` | Feature | What You Get | |---------|--------------| -| `jwt` | JWT authentication with `AuthUser` extractor | -| `cors` | CORS middleware with builder pattern | -| `rate-limit` | IP-based rate limiting | -| `toon` | LLM-optimized TOON format | -| `swagger-ui` | Auto-generated `/docs` endpoint | +| `extras-jwt` | JWT authentication with `AuthUser` extractor | +| `extras-cors` | CORS middleware with builder pattern | +| `extras-rate-limit` | IP-based rate limiting | +| `protocol-toon` | LLM-optimized TOON format | +| `core-openapi` | Auto-generated `/docs` endpoint | | `full` | All features enabled | ### 5. 🤖 LLM-First Design @@ -219,7 +219,7 @@ async fn handler(Json(body): Json) -> Json ### Short Term (v0.x) - Polish existing features -- Performance optimizations (simd-json, better allocations) +- Performance optimizations (`core-simd-json`, better allocations) - More middleware (compression, static files) ### Medium Term (v1.0) diff --git a/docs/cookbook/src/concepts/performance.md b/docs/cookbook/src/concepts/performance.md index 6b0c2833..ae9f6929 100644 --- a/docs/cookbook/src/concepts/performance.md +++ b/docs/cookbook/src/concepts/performance.md @@ -80,7 +80,7 @@ Performance is not a guessing game. Below are results from our internal benchmar | Framework | Requests/sec | Latency (avg) | Memory | |-----------|--------------|---------------|--------| | **RustAPI** | **~185,000** | **~0.54ms** | **~8MB** | -| **RustAPI + simd-json** | **~220,000** | **~0.45ms** | **~8MB** | +| **RustAPI + core-simd-json** | **~220,000** | **~0.45ms** | **~8MB** | | Actix-web | ~178,000 | ~0.56ms | ~10MB | | Axum | ~165,000 | ~0.61ms | ~12MB | | Rocket | ~95,000 | ~1.05ms | ~15MB | @@ -105,7 +105,7 @@ cd benches | Optimization | Description | |--------------|-------------| -| ⚡ **SIMD-JSON** | 2-4x faster JSON parsing with `simd-json` feature | +| ⚡ **SIMD-JSON** | 2-4x faster JSON parsing with `core-simd-json` feature | | 🔄 **Zero-copy parsing** | Direct memory access for path/query params | | 📦 **SmallVec PathParams** | Stack-optimized path parameters | | 🎯 **Compile-time dispatch** | All extractors resolved at compile time | diff --git a/docs/cookbook/src/learning/README.md b/docs/cookbook/src/learning/README.md index 7efe7f2a..9868b72c 100644 --- a/docs/cookbook/src/learning/README.md +++ b/docs/cookbook/src/learning/README.md @@ -190,14 +190,14 @@ Find examples by the RustAPI features they demonstrate: | `State` extractor | `crud-api`, `auth-api`, `sqlx-crud` | | `Json` extractor | `crud-api`, `auth-api`, `graphql-api` | | `ValidatedJson` | `auth-api`, `crud-api` | -| JWT (`jwt` feature) | `auth-api`, `microservices` | -| CORS (`cors` feature) | `middleware-chain`, `auth-api` | +| JWT (`extras-jwt` feature) | `auth-api`, `microservices` | +| CORS (`extras-cors` feature) | `middleware-chain`, `auth-api` | | Rate Limiting | `rate-limit-demo`, `auth-api` | -| WebSockets (`ws` feature) | `websocket`, `graphql-api` | -| TOON (`toon` feature) | `toon-api`, `mcp-server` | +| WebSockets (`protocol-ws` feature) | `websocket`, `graphql-api` | +| TOON (`protocol-toon` feature) | `toon-api`, `mcp-server` | | OAuth2 (`oauth2-client`) | `auth-api` (extended) | | Circuit Breaker | `microservices` | -| Replay (`replay` feature) | `microservices` (conceptual) | +| Replay (`extras-replay` feature) | `microservices` (conceptual) | | OpenTelemetry (`otel`) | `microservices-advanced` | | OpenAPI/Swagger | All examples | diff --git a/docs/cookbook/src/recipes/jwt_auth.md b/docs/cookbook/src/recipes/jwt_auth.md index b7b21af3..054367bb 100644 --- a/docs/cookbook/src/recipes/jwt_auth.md +++ b/docs/cookbook/src/recipes/jwt_auth.md @@ -1,14 +1,14 @@ # JWT Authentication -Authentication is critical for almost every API. RustAPI provides a built-in, production-ready JWT authentication system via the `jwt` feature. +Authentication is critical for almost every API. RustAPI provides a built-in, production-ready JWT authentication system via the `extras-jwt` feature. ## Dependencies -Enable the `jwt` feature in your `Cargo.toml`: +Enable the `extras-jwt` feature in your `Cargo.toml`: ```toml [dependencies] -rustapi-rs = { version = "0.1.335", features = ["jwt"] } +rustapi-rs = { version = "0.1.335", features = ["extras-jwt"] } serde = { version = "1", features = ["derive"] } ``` From 757f25e8942a5a42bedfc187cdc8758e091694a9 Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 11:39:18 +0300 Subject: [PATCH 03/10] Update path_validation.rs --- crates/rustapi-core/src/path_validation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rustapi-core/src/path_validation.rs b/crates/rustapi-core/src/path_validation.rs index 14dd4ca7..7f06fe46 100644 --- a/crates/rustapi-core/src/path_validation.rs +++ b/crates/rustapi-core/src/path_validation.rs @@ -136,7 +136,7 @@ impl std::error::Error for PathValidationError {} /// /// # Examples /// -/// ``` +/// ```rust,ignore /// use rustapi_core::path_validation::validate_path; /// /// // Valid paths From 838eccd9f0015edc9ca8ca11248d50e8727e74f4 Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 11:55:23 +0300 Subject: [PATCH 04/10] Update public-api.yml --- .github/workflows/public-api.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/public-api.yml b/.github/workflows/public-api.yml index efab4de4..22a85fc7 100644 --- a/.github/workflows/public-api.yml +++ b/.github/workflows/public-api.yml @@ -24,7 +24,9 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Install cargo-public-api - uses: taiki-e/install-action@cargo-public-api + uses: taiki-e/install-action@v2 + with: + tool: cargo-public-api - name: Generate current snapshots run: | From fdbd1e4d1bdf423db40eb07273b78e64fd0119c7 Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 13:47:40 +0300 Subject: [PATCH 05/10] Update public-api.yml --- .github/workflows/public-api.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/public-api.yml b/.github/workflows/public-api.yml index 22a85fc7..f29c391a 100644 --- a/.github/workflows/public-api.yml +++ b/.github/workflows/public-api.yml @@ -30,8 +30,8 @@ jobs: - name: Generate current snapshots run: | - cargo public-api -p rustapi-rs -s > /tmp/rustapi-rs.default.txt - cargo public-api -p rustapi-rs --all-features -s > /tmp/rustapi-rs.all-features.txt + cargo +stable public-api --manifest-path crates/rustapi-rs/Cargo.toml -s > /tmp/rustapi-rs.default.txt + cargo +stable public-api --manifest-path crates/rustapi-rs/Cargo.toml --all-features -s > /tmp/rustapi-rs.all-features.txt - name: Check snapshot drift run: | From ed54461feff923cb407d0303c3ca0b656a36df7a Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 14:33:58 +0300 Subject: [PATCH 06/10] Update public-api.yml --- .github/workflows/public-api.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/public-api.yml b/.github/workflows/public-api.yml index f29c391a..421b6da5 100644 --- a/.github/workflows/public-api.yml +++ b/.github/workflows/public-api.yml @@ -23,6 +23,9 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable + - name: Install Nightly Toolchain (for rustdoc JSON) + uses: dtolnay/rust-toolchain@nightly + - name: Install cargo-public-api uses: taiki-e/install-action@v2 with: From 8cf5886ccabaef5afb1bdd01409f06a0339ee851 Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 15:03:04 +0300 Subject: [PATCH 07/10] Update public-api.yml --- .github/workflows/public-api.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/public-api.yml b/.github/workflows/public-api.yml index 421b6da5..ae87da0e 100644 --- a/.github/workflows/public-api.yml +++ b/.github/workflows/public-api.yml @@ -26,6 +26,9 @@ jobs: - name: Install Nightly Toolchain (for rustdoc JSON) uses: dtolnay/rust-toolchain@nightly + - name: Ensure nightly is installed + run: rustup toolchain install nightly --profile minimal + - name: Install cargo-public-api uses: taiki-e/install-action@v2 with: From 7d49023932b980e206aceadfb96ba8ed072e2cdc Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 15:13:28 +0300 Subject: [PATCH 08/10] Enforce LF for api/public/*.txt Add a .gitattributes rule to treat api/public/*.txt as text with LF endings. Normalize the EOLs for rustapi-rs.all-features.txt and rustapi-rs.default.txt (appearing as binary diffs due to line-ending changes). --- .gitattributes | 1 + api/public/rustapi-rs.all-features.txt | Bin 28810 -> 14024 bytes api/public/rustapi-rs.default.txt | Bin 15508 -> 7537 bytes 3 files changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index dfe07704..0932d6a8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ # Auto detect text files and perform LF normalization * text=auto +api/public/*.txt text eol=lf diff --git a/api/public/rustapi-rs.all-features.txt b/api/public/rustapi-rs.all-features.txt index 71f82a40ae57a9511029edf7a42e6e84ce9ffba9..42d89b56a1b3d19acd1035c421a0c1fa260d3137 100644 GIT binary patch literal 14024 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 diff --git a/api/public/rustapi-rs.default.txt b/api/public/rustapi-rs.default.txt index 0633c1f28a46f4dad107a9a2236ed2ede1402803..35d17c60499b859ed1aa072e9172c19929538703 100644 GIT binary patch literal 7537 zcmb7}O>@&Q5Qgvkik>-g^f1hn(hdd6lmyB#quOet8OJiR94NmYIdWv#*>^WwvyWEa z(i6GuA1`*cx^R8xRckh`d)0=Y`c7v*6~)`PM_{K)XSIDYAn4R%o)QhI87|O1_(`pAZJSb;Wt=79ZFZqF9slEX(;3j?8>8zAkK2v2yOsXc>yA#I#qX8#^c;RvO;v+Y8)Iwj_NLTi z2R>?5X%{B1=i;DhzpKflw~e%FhVo(B=}PXE-;%|?_1e+vf2AwqbV=vq7RVU8rPpqX;%&uF z(lH44Z2-b}lbt=x5sq++_E7Xr`H~(+X7*R%vACs@LAJKJN&-U!nH6khh~wP zTe!Fn5w4^KS4A5FT)Cd^OWSj$a1Xk0qv%aIwaET;o!+?o_@AptQIyuvD|!eN1uGjP za-MQT819fMEa=AsG7>UDoQgz>6_OEQv?LOeoQxE!D3K9FWrPG>Iiaw$M3lP)W&|fN zBPeK0PSHojNFb$&VNq-%2K6R}dC7^4s5&FY3eSRJZkh#&kBDX z%)*M+h(O(%;*4}HA-Tc4fF1L(8Wtf@#zqV(Sr!*|mZ)bjl_Y61iuqd~IZ@b#i~U?} zizyj-%K=7vB8EgRMMa&PNUTJW!D^Sq#TE}X$LxMV_Qslk@};QQ3aPKW1jc$q71V#c zQ}IS#T4L*T-GjeAV@zD_G!_xw!rEP+cw9HE73Vi>1dLgy@tb-C3$#jmsQ zFY26)el7-{%TnuEotr3VbTfpJI)XcE#CWyKva)uUI8pE>5<1=-k@qG4Oqa5WLYT;h zH^-+rekn=rI3*On3MEu9$i!9pv8UheGXy^SEf_|E5~<;buY@4Q%z<;NGR>3aM;;4i zs_TH9QybLGDV6Z77!9o`!aJhSZ1$ls)rXZcA-r`?*zyf>`Ry~2!9T&bv4s3qE`HKH MRaEP3_*e7lAFiqdG5`Po literal 15508 zcmcJWL2p|(5QKRS(EreLjy(oNfmBrxw{g+fX?ipiNpTIxmMbYq-5=j}hRnkwMLsz@ z%fN^o+1lZ9xm=Q;`2PF%K70u8!nffzY{LpacX+)Ihp-6u;U-++^*($Pp5Rq_AOE_Z zc;B<|EPRizV~ty=E%DneZ26w2;Rk$KRG&w0=Fp(Z&S<+258)0TyoDbd^lF8l2lQeU zeuhU|{LSLak10&pxNvTP){j?<4%>=-;8wsePNm-*L?EFyc4nF@FJ@HF~}ZS`NO5p5KNG zsQiVQw1yY9xVII4i{p5Jnk5!5MT399ac(bChwimN1R7FS)by&kXB1IC^j zT!F2=>|n!vr!uqNpq}w8aD&QwEmns&sHFGs!B(3_E2W12jutnFSK70zq;V`y9}Aqi zteH?MsOOhI5Phy;LtZi$EIF`595&H+u1i;l3HKG-cvc+hpG}cj$XIYiws?};@uWJm zZqc}o?bAa~5P(c5H>Sw@MxLT^vYC7uX& zab>web>iO5;_)64TE_@Y?|x2E8j=;xP1a{iyMqnwJ)EV~>C0SlZDrkQ*?U<`OOrKq zSRd6f82YC(n%pxFuPJp>zwGQLSDCwOW~t_3I=Rw#melx8sm1EyVLgwlaSy|@xNV)y zb@j8xw2IDyN=+{tno{#b%g&5)FM9B!Tu)(1xxJ4oo|SO{bM+n3s3K&Yn)I zjsV9eT&Hw~9fT)p z3jZp7Qch!Sg+6Q5$hb5{*7nV|$1gQ<0;Z2ut}^RT;p|K;@0EwIl{yM*YbmJQ?J=!c zmstCF+_8_t*~41_dT_ejw~@Z7c|Iz5*SH=mFYjAPp)dQ( zJfZP`V;LJObF8g<#Kb?! zJ}#N}st2Q72T~h;4pg4UA7h*Mk*saNbvef^8B$t$Ip^0`n_{QlK2Pat_u%R{D7%hn z6)&Hyl|3x9rIvPEb|PA9{e+B8vy^x8ct`OccaDDz&3T-={NziyMOXLxC3@+*2XSSgZI*V69i*)ACiVT5l$vyS~I%jpfzKKB8CaDQBKq zMWcJQ4yl^T_}XeCqw{48JsY*GyzepJbn4euR@vW_S|aD(2rAsJ<s58{2~_vo8%9*ius zmrlRYQ^;x^_d%tnUslT6^2&49v)0pyYpuhbl71hwrAI}^e)@)}opB@25)TxAEp`dM59G;3W|! From 6fc428b0c55525e6c4b26ef1972fa3e4ab3bc806 Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 22:12:25 +0300 Subject: [PATCH 09/10] Normalize .sh line endings; improve label detection Add .github/scripts/*.sh to .gitattributes to enforce LF normalization for shell scripts. Enhance public_api_label_gate.sh to collect labels from PR_LABELS or, if missing, from the GitHub event payload (using jq), normalize labels to lowercase, and print detected labels before checking for required "breaking" or "feature" labels. --- .gitattributes | 1 + .github/scripts/public_api_label_gate.sh | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 0932d6a8..a100e591 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ # Auto detect text files and perform LF normalization * text=auto api/public/*.txt text eol=lf +.github/scripts/*.sh text eol=lf diff --git a/.github/scripts/public_api_label_gate.sh b/.github/scripts/public_api_label_gate.sh index a9f6afcf..30f91a17 100644 --- a/.github/scripts/public_api_label_gate.sh +++ b/.github/scripts/public_api_label_gate.sh @@ -15,8 +15,18 @@ if [[ -z "$changed_snapshots" ]]; then exit 0 fi -labels=",${PR_LABELS:-}," -if [[ "$labels" == *",breaking,"* || "$labels" == *",feature,"* ]]; then +# Collect labels from env first, then fall back to GitHub event payload. +labels_csv="${PR_LABELS:-}" +if [[ -z "$labels_csv" && -n "${GITHUB_EVENT_PATH:-}" && -f "${GITHUB_EVENT_PATH}" ]]; then + if command -v jq >/dev/null 2>&1; then + labels_csv="$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH" 2>/dev/null | paste -sd ',' -)" + fi +fi + +labels_normalized=",$(echo "$labels_csv" | tr '[:upper:]' '[:lower:]')," +echo "Detected PR labels: ${labels_csv:-}" + +if [[ "$labels_normalized" == *",breaking,"* || "$labels_normalized" == *",feature,"* ]]; then echo "Public API snapshots changed and required label is present." exit 0 fi From 3724bbd001413f7c38854464c6ffd6c2c038f6a4 Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 13 Feb 2026 22:38:41 +0300 Subject: [PATCH 10/10] Delete public-api.yml --- .github/workflows/public-api.yml | 63 -------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 .github/workflows/public-api.yml diff --git a/.github/workflows/public-api.yml b/.github/workflows/public-api.yml deleted file mode 100644 index ae87da0e..00000000 --- a/.github/workflows/public-api.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Public API - -on: - push: - branches: [main] - pull_request: - branches: [main] - -permissions: - contents: read - pull-requests: read - -env: - CARGO_TERM_COLOR: always - -jobs: - snapshot: - name: Snapshot Drift - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - - name: Install Nightly Toolchain (for rustdoc JSON) - uses: dtolnay/rust-toolchain@nightly - - - name: Ensure nightly is installed - run: rustup toolchain install nightly --profile minimal - - - name: Install cargo-public-api - uses: taiki-e/install-action@v2 - with: - tool: cargo-public-api - - - name: Generate current snapshots - run: | - cargo +stable public-api --manifest-path crates/rustapi-rs/Cargo.toml -s > /tmp/rustapi-rs.default.txt - cargo +stable public-api --manifest-path crates/rustapi-rs/Cargo.toml --all-features -s > /tmp/rustapi-rs.all-features.txt - - - name: Check snapshot drift - run: | - diff -u api/public/rustapi-rs.default.txt /tmp/rustapi-rs.default.txt - diff -u api/public/rustapi-rs.all-features.txt /tmp/rustapi-rs.all-features.txt - - label-gate: - name: Label Gate - runs-on: ubuntu-latest - needs: snapshot - if: github.event_name == 'pull_request' - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Enforce labels when snapshots change - env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} - HEAD_SHA: ${{ github.sha }} - PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} - run: | - bash .github/scripts/public_api_label_gate.sh