diff --git a/.gitattributes b/.gitattributes index dfe07704..a100e591 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +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/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/.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..30f91a17 --- /dev/null +++ b/.github/scripts/public_api_label_gate.sh @@ -0,0 +1,37 @@ +#!/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 + +# 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 + +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/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 new file mode 100644 index 00000000..d151d0be --- /dev/null +++ b/CONTRACT.md @@ -0,0 +1,67 @@ +# 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. +- 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 + +- 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. +- 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 + +- 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 00000000..42d89b56 --- /dev/null +++ b/api/public/rustapi-rs.all-features.txt @@ -0,0 +1,380 @@ +pub mod rustapi_rs +pub use rustapi_rs::<> +pub use rustapi_rs::AllowedOrigins +pub use rustapi_rs::ApiError +pub use rustapi_rs::AsyncValidatedJson +pub use rustapi_rs::AuthUser +pub use rustapi_rs::Body +pub use rustapi_rs::BodyLimitLayer +pub use rustapi_rs::BodyStream +pub use rustapi_rs::BodyVariant +pub use rustapi_rs::ClientIp +pub use rustapi_rs::CompressionAlgorithm +pub use rustapi_rs::CompressionConfig +pub use rustapi_rs::CompressionLayer +pub use rustapi_rs::Config +pub use rustapi_rs::ConfigError +pub use rustapi_rs::Cookies +pub use rustapi_rs::CorsLayer +pub use rustapi_rs::Created +pub use rustapi_rs::Environment +pub use rustapi_rs::Extension +pub use rustapi_rs::ExtrasEnvironment +pub use rustapi_rs::FieldError +pub use rustapi_rs::FromRequest +pub use rustapi_rs::FromRequestParts +pub use rustapi_rs::Handler +pub use rustapi_rs::HandlerService +pub use rustapi_rs::HeaderValue +pub use rustapi_rs::Headers +pub use rustapi_rs::Html +pub use rustapi_rs::Http3Config +pub use rustapi_rs::Http3Server +pub use rustapi_rs::IntoResponse +pub use rustapi_rs::Json +pub use rustapi_rs::JwtError +pub use rustapi_rs::JwtLayer +pub use rustapi_rs::JwtValidation +pub use rustapi_rs::KeepAlive +pub use rustapi_rs::MethodRouter +pub use rustapi_rs::Multipart +pub use rustapi_rs::MultipartConfig +pub use rustapi_rs::MultipartField +pub use rustapi_rs::NoContent +pub use rustapi_rs::Path +pub use rustapi_rs::Query +pub use rustapi_rs::RateLimitLayer +pub use rustapi_rs::Redirect +pub use rustapi_rs::Request +pub use rustapi_rs::RequestId +pub use rustapi_rs::RequestIdLayer +pub use rustapi_rs::Response +pub use rustapi_rs::ResponseBody +pub use rustapi_rs::Result +pub use rustapi_rs::Route +pub use rustapi_rs::RouteHandler +pub use rustapi_rs::RouteMatch +pub use rustapi_rs::Router +pub use rustapi_rs::RustApi +pub use rustapi_rs::RustApiConfig +pub use rustapi_rs::SqlxErrorExt +pub use rustapi_rs::Sse +pub use rustapi_rs::SseEvent +pub use rustapi_rs::State +pub use rustapi_rs::StaticFile +pub use rustapi_rs::StaticFileConfig +pub use rustapi_rs::StatusCode +pub use rustapi_rs::StreamBody +pub use rustapi_rs::TracingLayer +pub use rustapi_rs::Typed +pub use rustapi_rs::TypedPath +pub use rustapi_rs::UploadedFile +pub use rustapi_rs::Validatable +pub use rustapi_rs::ValidatedClaims +pub use rustapi_rs::ValidatedJson +pub use rustapi_rs::WithStatus +pub use rustapi_rs::api_key +pub use rustapi_rs::cache +pub use rustapi_rs::circuit_breaker +pub use rustapi_rs::collect_auto_routes +pub use rustapi_rs::config +pub use rustapi_rs::convert_sqlx_error +pub use rustapi_rs::cors +pub use rustapi_rs::create_token +pub use rustapi_rs::dedup +pub use rustapi_rs::delete +pub use rustapi_rs::delete_route +pub use rustapi_rs::env_or +pub use rustapi_rs::env_parse +pub use rustapi_rs::get +pub use rustapi_rs::get_environment +pub use rustapi_rs::get_route +pub use rustapi_rs::guard +pub use rustapi_rs::jwt +pub use rustapi_rs::load_dotenv +pub use rustapi_rs::load_dotenv_from +pub use rustapi_rs::logging +pub use rustapi_rs::otel +pub use rustapi_rs::patch +pub use rustapi_rs::patch_route +pub use rustapi_rs::post +pub use rustapi_rs::post_route +pub use rustapi_rs::put +pub use rustapi_rs::put_route +pub use rustapi_rs::rate_limit +pub use rustapi_rs::replay +pub use rustapi_rs::require_env +pub use rustapi_rs::retry +pub use rustapi_rs::route +pub use rustapi_rs::sanitization +pub use rustapi_rs::security_headers +pub use rustapi_rs::serve_dir +pub use rustapi_rs::sse_response +pub use rustapi_rs::structured_logging +pub use rustapi_rs::timeout +pub mod rustapi_rs::core +pub use rustapi_rs::core::ApiError +pub use rustapi_rs::core::AsyncValidatedJson +pub use rustapi_rs::core::Body +pub use rustapi_rs::core::BodyLimitLayer +pub use rustapi_rs::core::BodyStream +pub use rustapi_rs::core::BodyVariant +pub use rustapi_rs::core::ClientIp +pub use rustapi_rs::core::CompressionAlgorithm +pub use rustapi_rs::core::CompressionConfig +pub use rustapi_rs::core::CompressionLayer +pub use rustapi_rs::core::Cookies +pub use rustapi_rs::core::Created +pub use rustapi_rs::core::Environment +pub use rustapi_rs::core::Extension +pub use rustapi_rs::core::FieldError +pub use rustapi_rs::core::FromRequest +pub use rustapi_rs::core::FromRequestParts +pub use rustapi_rs::core::Handler +pub use rustapi_rs::core::HandlerService +pub use rustapi_rs::core::HeaderValue +pub use rustapi_rs::core::Headers +pub use rustapi_rs::core::Html +pub use rustapi_rs::core::Http3Config +pub use rustapi_rs::core::Http3Server +pub use rustapi_rs::core::IntoResponse +pub use rustapi_rs::core::Json +pub use rustapi_rs::core::KeepAlive +pub use rustapi_rs::core::MethodRouter +pub use rustapi_rs::core::Multipart +pub use rustapi_rs::core::MultipartConfig +pub use rustapi_rs::core::MultipartField +pub use rustapi_rs::core::NoContent +pub use rustapi_rs::core::Path +pub use rustapi_rs::core::Query +pub use rustapi_rs::core::Redirect +pub use rustapi_rs::core::Request +pub use rustapi_rs::core::RequestId +pub use rustapi_rs::core::RequestIdLayer +pub use rustapi_rs::core::Response +pub use rustapi_rs::core::ResponseBody +pub use rustapi_rs::core::Result +pub use rustapi_rs::core::Route +pub use rustapi_rs::core::RouteHandler +pub use rustapi_rs::core::RouteMatch +pub use rustapi_rs::core::Router +pub use rustapi_rs::core::RustApi +pub use rustapi_rs::core::RustApiConfig +pub use rustapi_rs::core::Sse +pub use rustapi_rs::core::SseEvent +pub use rustapi_rs::core::State +pub use rustapi_rs::core::StaticFile +pub use rustapi_rs::core::StaticFileConfig +pub use rustapi_rs::core::StatusCode +pub use rustapi_rs::core::StreamBody +pub use rustapi_rs::core::TracingLayer +pub use rustapi_rs::core::Typed +pub use rustapi_rs::core::TypedPath +pub use rustapi_rs::core::UploadedFile +pub use rustapi_rs::core::Validatable +pub use rustapi_rs::core::ValidatedJson +pub use rustapi_rs::core::WithStatus +pub use rustapi_rs::core::collect_auto_routes +pub use rustapi_rs::core::delete +pub use rustapi_rs::core::delete_route +pub use rustapi_rs::core::get +pub use rustapi_rs::core::get_environment +pub use rustapi_rs::core::get_route +pub use rustapi_rs::core::patch +pub use rustapi_rs::core::patch_route +pub use rustapi_rs::core::post +pub use rustapi_rs::core::post_route +pub use rustapi_rs::core::put +pub use rustapi_rs::core::put_route +pub use rustapi_rs::core::route +pub use rustapi_rs::core::serve_dir +pub use rustapi_rs::core::sse_response +pub mod rustapi_rs::extras +pub mod rustapi_rs::extras::api_key +pub use rustapi_rs::extras::api_key::api_key +pub mod rustapi_rs::extras::cache +pub use rustapi_rs::extras::cache::cache +pub mod rustapi_rs::extras::circuit_breaker +pub use rustapi_rs::extras::circuit_breaker::circuit_breaker +pub mod rustapi_rs::extras::config +pub use rustapi_rs::extras::config::Config +pub use rustapi_rs::extras::config::ConfigError +pub use rustapi_rs::extras::config::Environment +pub use rustapi_rs::extras::config::config +pub use rustapi_rs::extras::config::env_or +pub use rustapi_rs::extras::config::env_parse +pub use rustapi_rs::extras::config::load_dotenv +pub use rustapi_rs::extras::config::load_dotenv_from +pub use rustapi_rs::extras::config::require_env +pub mod rustapi_rs::extras::cors +pub use rustapi_rs::extras::cors::AllowedOrigins +pub use rustapi_rs::extras::cors::CorsLayer +pub use rustapi_rs::extras::cors::cors +pub mod rustapi_rs::extras::dedup +pub use rustapi_rs::extras::dedup::dedup +pub mod rustapi_rs::extras::guard +pub use rustapi_rs::extras::guard::guard +pub mod rustapi_rs::extras::insight +pub use rustapi_rs::extras::insight::insight +pub mod rustapi_rs::extras::jwt +pub use rustapi_rs::extras::jwt::AuthUser +pub use rustapi_rs::extras::jwt::JwtError +pub use rustapi_rs::extras::jwt::JwtLayer +pub use rustapi_rs::extras::jwt::JwtValidation +pub use rustapi_rs::extras::jwt::ValidatedClaims +pub use rustapi_rs::extras::jwt::create_token +pub use rustapi_rs::extras::jwt::jwt +pub mod rustapi_rs::extras::logging +pub use rustapi_rs::extras::logging::logging +pub mod rustapi_rs::extras::otel +pub use rustapi_rs::extras::otel::otel +pub mod rustapi_rs::extras::rate_limit +pub use rustapi_rs::extras::rate_limit::RateLimitLayer +pub use rustapi_rs::extras::rate_limit::rate_limit +pub mod rustapi_rs::extras::replay +pub use rustapi_rs::extras::replay::replay +pub mod rustapi_rs::extras::retry +pub use rustapi_rs::extras::retry::retry +pub mod rustapi_rs::extras::sanitization +pub use rustapi_rs::extras::sanitization::sanitization +pub mod rustapi_rs::extras::security_headers +pub use rustapi_rs::extras::security_headers::security_headers +pub mod rustapi_rs::extras::sqlx +pub use rustapi_rs::extras::sqlx::SqlxErrorExt +pub use rustapi_rs::extras::sqlx::convert_sqlx_error +pub mod rustapi_rs::extras::structured_logging +pub use rustapi_rs::extras::structured_logging::structured_logging +pub mod rustapi_rs::extras::timeout +pub use rustapi_rs::extras::timeout::timeout +pub mod rustapi_rs::grpc +pub use rustapi_rs::grpc::<> +pub mod rustapi_rs::prelude +pub use rustapi_rs::prelude::AcceptHeader +pub use rustapi_rs::prelude::AllowedOrigins +pub use rustapi_rs::prelude::ApiError +pub use rustapi_rs::prelude::ApiError +pub use rustapi_rs::prelude::AsyncValidate +pub use rustapi_rs::prelude::AsyncValidatedJson +pub use rustapi_rs::prelude::AuthUser +pub use rustapi_rs::prelude::Body +pub use rustapi_rs::prelude::BodyLimitLayer +pub use rustapi_rs::prelude::Broadcast +pub use rustapi_rs::prelude::ClientIp +pub use rustapi_rs::prelude::CompressionAlgorithm +pub use rustapi_rs::prelude::CompressionConfig +pub use rustapi_rs::prelude::CompressionLayer +pub use rustapi_rs::prelude::Config +pub use rustapi_rs::prelude::ConfigError +pub use rustapi_rs::prelude::ContextBuilder +pub use rustapi_rs::prelude::Cookies +pub use rustapi_rs::prelude::CorsLayer +pub use rustapi_rs::prelude::Created +pub use rustapi_rs::prelude::Deserialize +pub use rustapi_rs::prelude::Deserialize +pub use rustapi_rs::prelude::Extension +pub use rustapi_rs::prelude::ExtrasEnvironment +pub use rustapi_rs::prelude::HeaderValue +pub use rustapi_rs::prelude::Headers +pub use rustapi_rs::prelude::Html +pub use rustapi_rs::prelude::IntoResponse +pub use rustapi_rs::prelude::Json +pub use rustapi_rs::prelude::JwtError +pub use rustapi_rs::prelude::JwtLayer +pub use rustapi_rs::prelude::JwtValidation +pub use rustapi_rs::prelude::KeepAlive +pub use rustapi_rs::prelude::LlmResponse +pub use rustapi_rs::prelude::Message +pub use rustapi_rs::prelude::Multipart +pub use rustapi_rs::prelude::MultipartConfig +pub use rustapi_rs::prelude::MultipartField +pub use rustapi_rs::prelude::Negotiate +pub use rustapi_rs::prelude::NoContent +pub use rustapi_rs::prelude::OutputFormat +pub use rustapi_rs::prelude::Path +pub use rustapi_rs::prelude::Query +pub use rustapi_rs::prelude::RateLimitLayer +pub use rustapi_rs::prelude::Redirect +pub use rustapi_rs::prelude::Request +pub use rustapi_rs::prelude::RequestId +pub use rustapi_rs::prelude::RequestIdLayer +pub use rustapi_rs::prelude::Response +pub use rustapi_rs::prelude::Result +pub use rustapi_rs::prelude::Route +pub use rustapi_rs::prelude::Router +pub use rustapi_rs::prelude::RustApi +pub use rustapi_rs::prelude::RustApiConfig +pub use rustapi_rs::prelude::Schema +pub use rustapi_rs::prelude::Serialize +pub use rustapi_rs::prelude::Serialize +pub use rustapi_rs::prelude::SqlxErrorExt +pub use rustapi_rs::prelude::Sse +pub use rustapi_rs::prelude::SseEvent +pub use rustapi_rs::prelude::State +pub use rustapi_rs::prelude::StaticFile +pub use rustapi_rs::prelude::StaticFileConfig +pub use rustapi_rs::prelude::StatusCode +pub use rustapi_rs::prelude::StreamBody +pub use rustapi_rs::prelude::Templates +pub use rustapi_rs::prelude::TemplatesConfig +pub use rustapi_rs::prelude::Toon +pub use rustapi_rs::prelude::TracingLayer +pub use rustapi_rs::prelude::Typed +pub use rustapi_rs::prelude::TypedPath +pub use rustapi_rs::prelude::TypedPath +pub use rustapi_rs::prelude::UploadedFile +pub use rustapi_rs::prelude::V2Validate +pub use rustapi_rs::prelude::Validatable +pub use rustapi_rs::prelude::Validate +pub use rustapi_rs::prelude::Validate +pub use rustapi_rs::prelude::ValidatedClaims +pub use rustapi_rs::prelude::ValidatedJson +pub use rustapi_rs::prelude::View +pub use rustapi_rs::prelude::WebSocket +pub use rustapi_rs::prelude::WebSocketStream +pub use rustapi_rs::prelude::WithStatus +pub use rustapi_rs::prelude::convert_sqlx_error +pub use rustapi_rs::prelude::create_token +pub use rustapi_rs::prelude::debug +pub use rustapi_rs::prelude::delete +pub use rustapi_rs::prelude::delete_route +pub use rustapi_rs::prelude::env_or +pub use rustapi_rs::prelude::env_parse +pub use rustapi_rs::prelude::error +pub use rustapi_rs::prelude::get +pub use rustapi_rs::prelude::get_route +pub use rustapi_rs::prelude::info +pub use rustapi_rs::prelude::load_dotenv +pub use rustapi_rs::prelude::load_dotenv_from +pub use rustapi_rs::prelude::patch +pub use rustapi_rs::prelude::patch_route +pub use rustapi_rs::prelude::post +pub use rustapi_rs::prelude::post_route +pub use rustapi_rs::prelude::put +pub use rustapi_rs::prelude::put_route +pub use rustapi_rs::prelude::require_env +pub use rustapi_rs::prelude::route +pub use rustapi_rs::prelude::run_concurrently +pub use rustapi_rs::prelude::run_rustapi_and_grpc +pub use rustapi_rs::prelude::run_rustapi_and_grpc_with_shutdown +pub use rustapi_rs::prelude::serve_dir +pub use rustapi_rs::prelude::sse_response +pub use rustapi_rs::prelude::trace +pub use rustapi_rs::prelude::warn +pub mod rustapi_rs::protocol +pub mod rustapi_rs::protocol::grpc +pub use rustapi_rs::protocol::grpc::<> +pub mod rustapi_rs::protocol::http3 +pub use rustapi_rs::protocol::http3::Http3Config +pub use rustapi_rs::protocol::http3::Http3Server +pub mod rustapi_rs::protocol::toon +pub use rustapi_rs::protocol::toon::<> +pub mod rustapi_rs::protocol::view +pub use rustapi_rs::protocol::view::<> +pub mod rustapi_rs::protocol::ws +pub use rustapi_rs::protocol::ws::<> +pub mod rustapi_rs::toon +pub use rustapi_rs::toon::<> +pub mod rustapi_rs::view +pub use rustapi_rs::view::<> +pub mod rustapi_rs::ws +pub use rustapi_rs::ws::<> diff --git a/api/public/rustapi-rs.default.txt b/api/public/rustapi-rs.default.txt new file mode 100644 index 00000000..35d17c60 --- /dev/null +++ b/api/public/rustapi-rs.default.txt @@ -0,0 +1,216 @@ +pub mod rustapi_rs +pub use rustapi_rs::<> +pub use rustapi_rs::ApiError +pub use rustapi_rs::AsyncValidatedJson +pub use rustapi_rs::Body +pub use rustapi_rs::BodyLimitLayer +pub use rustapi_rs::BodyStream +pub use rustapi_rs::BodyVariant +pub use rustapi_rs::ClientIp +pub use rustapi_rs::Created +pub use rustapi_rs::Environment +pub use rustapi_rs::Extension +pub use rustapi_rs::FieldError +pub use rustapi_rs::FromRequest +pub use rustapi_rs::FromRequestParts +pub use rustapi_rs::Handler +pub use rustapi_rs::HandlerService +pub use rustapi_rs::HeaderValue +pub use rustapi_rs::Headers +pub use rustapi_rs::Html +pub use rustapi_rs::IntoResponse +pub use rustapi_rs::Json +pub use rustapi_rs::KeepAlive +pub use rustapi_rs::MethodRouter +pub use rustapi_rs::Multipart +pub use rustapi_rs::MultipartConfig +pub use rustapi_rs::MultipartField +pub use rustapi_rs::NoContent +pub use rustapi_rs::Path +pub use rustapi_rs::Query +pub use rustapi_rs::Redirect +pub use rustapi_rs::Request +pub use rustapi_rs::RequestId +pub use rustapi_rs::RequestIdLayer +pub use rustapi_rs::Response +pub use rustapi_rs::ResponseBody +pub use rustapi_rs::Result +pub use rustapi_rs::Route +pub use rustapi_rs::RouteHandler +pub use rustapi_rs::RouteMatch +pub use rustapi_rs::Router +pub use rustapi_rs::RustApi +pub use rustapi_rs::RustApiConfig +pub use rustapi_rs::Sse +pub use rustapi_rs::SseEvent +pub use rustapi_rs::State +pub use rustapi_rs::StaticFile +pub use rustapi_rs::StaticFileConfig +pub use rustapi_rs::StatusCode +pub use rustapi_rs::StreamBody +pub use rustapi_rs::TracingLayer +pub use rustapi_rs::Typed +pub use rustapi_rs::TypedPath +pub use rustapi_rs::UploadedFile +pub use rustapi_rs::Validatable +pub use rustapi_rs::ValidatedJson +pub use rustapi_rs::WithStatus +pub use rustapi_rs::collect_auto_routes +pub use rustapi_rs::delete +pub use rustapi_rs::delete_route +pub use rustapi_rs::get +pub use rustapi_rs::get_environment +pub use rustapi_rs::get_route +pub use rustapi_rs::patch +pub use rustapi_rs::patch_route +pub use rustapi_rs::post +pub use rustapi_rs::post_route +pub use rustapi_rs::put +pub use rustapi_rs::put_route +pub use rustapi_rs::route +pub use rustapi_rs::serve_dir +pub use rustapi_rs::sse_response +pub mod rustapi_rs::core +pub use rustapi_rs::core::ApiError +pub use rustapi_rs::core::AsyncValidatedJson +pub use rustapi_rs::core::Body +pub use rustapi_rs::core::BodyLimitLayer +pub use rustapi_rs::core::BodyStream +pub use rustapi_rs::core::BodyVariant +pub use rustapi_rs::core::ClientIp +pub use rustapi_rs::core::Created +pub use rustapi_rs::core::Environment +pub use rustapi_rs::core::Extension +pub use rustapi_rs::core::FieldError +pub use rustapi_rs::core::FromRequest +pub use rustapi_rs::core::FromRequestParts +pub use rustapi_rs::core::Handler +pub use rustapi_rs::core::HandlerService +pub use rustapi_rs::core::HeaderValue +pub use rustapi_rs::core::Headers +pub use rustapi_rs::core::Html +pub use rustapi_rs::core::IntoResponse +pub use rustapi_rs::core::Json +pub use rustapi_rs::core::KeepAlive +pub use rustapi_rs::core::MethodRouter +pub use rustapi_rs::core::Multipart +pub use rustapi_rs::core::MultipartConfig +pub use rustapi_rs::core::MultipartField +pub use rustapi_rs::core::NoContent +pub use rustapi_rs::core::Path +pub use rustapi_rs::core::Query +pub use rustapi_rs::core::Redirect +pub use rustapi_rs::core::Request +pub use rustapi_rs::core::RequestId +pub use rustapi_rs::core::RequestIdLayer +pub use rustapi_rs::core::Response +pub use rustapi_rs::core::ResponseBody +pub use rustapi_rs::core::Result +pub use rustapi_rs::core::Route +pub use rustapi_rs::core::RouteHandler +pub use rustapi_rs::core::RouteMatch +pub use rustapi_rs::core::Router +pub use rustapi_rs::core::RustApi +pub use rustapi_rs::core::RustApiConfig +pub use rustapi_rs::core::Sse +pub use rustapi_rs::core::SseEvent +pub use rustapi_rs::core::State +pub use rustapi_rs::core::StaticFile +pub use rustapi_rs::core::StaticFileConfig +pub use rustapi_rs::core::StatusCode +pub use rustapi_rs::core::StreamBody +pub use rustapi_rs::core::TracingLayer +pub use rustapi_rs::core::Typed +pub use rustapi_rs::core::TypedPath +pub use rustapi_rs::core::UploadedFile +pub use rustapi_rs::core::Validatable +pub use rustapi_rs::core::ValidatedJson +pub use rustapi_rs::core::WithStatus +pub use rustapi_rs::core::collect_auto_routes +pub use rustapi_rs::core::delete +pub use rustapi_rs::core::delete_route +pub use rustapi_rs::core::get +pub use rustapi_rs::core::get_environment +pub use rustapi_rs::core::get_route +pub use rustapi_rs::core::patch +pub use rustapi_rs::core::patch_route +pub use rustapi_rs::core::post +pub use rustapi_rs::core::post_route +pub use rustapi_rs::core::put +pub use rustapi_rs::core::put_route +pub use rustapi_rs::core::route +pub use rustapi_rs::core::serve_dir +pub use rustapi_rs::core::sse_response +pub mod rustapi_rs::extras +pub mod rustapi_rs::prelude +pub use rustapi_rs::prelude::ApiError +pub use rustapi_rs::prelude::ApiError +pub use rustapi_rs::prelude::AsyncValidate +pub use rustapi_rs::prelude::AsyncValidatedJson +pub use rustapi_rs::prelude::Body +pub use rustapi_rs::prelude::BodyLimitLayer +pub use rustapi_rs::prelude::ClientIp +pub use rustapi_rs::prelude::Created +pub use rustapi_rs::prelude::Deserialize +pub use rustapi_rs::prelude::Deserialize +pub use rustapi_rs::prelude::Extension +pub use rustapi_rs::prelude::HeaderValue +pub use rustapi_rs::prelude::Headers +pub use rustapi_rs::prelude::Html +pub use rustapi_rs::prelude::IntoResponse +pub use rustapi_rs::prelude::Json +pub use rustapi_rs::prelude::KeepAlive +pub use rustapi_rs::prelude::Multipart +pub use rustapi_rs::prelude::MultipartConfig +pub use rustapi_rs::prelude::MultipartField +pub use rustapi_rs::prelude::NoContent +pub use rustapi_rs::prelude::Path +pub use rustapi_rs::prelude::Query +pub use rustapi_rs::prelude::Redirect +pub use rustapi_rs::prelude::Request +pub use rustapi_rs::prelude::RequestId +pub use rustapi_rs::prelude::RequestIdLayer +pub use rustapi_rs::prelude::Response +pub use rustapi_rs::prelude::Result +pub use rustapi_rs::prelude::Route +pub use rustapi_rs::prelude::Router +pub use rustapi_rs::prelude::RustApi +pub use rustapi_rs::prelude::RustApiConfig +pub use rustapi_rs::prelude::Schema +pub use rustapi_rs::prelude::Serialize +pub use rustapi_rs::prelude::Serialize +pub use rustapi_rs::prelude::Sse +pub use rustapi_rs::prelude::SseEvent +pub use rustapi_rs::prelude::State +pub use rustapi_rs::prelude::StaticFile +pub use rustapi_rs::prelude::StaticFileConfig +pub use rustapi_rs::prelude::StatusCode +pub use rustapi_rs::prelude::StreamBody +pub use rustapi_rs::prelude::TracingLayer +pub use rustapi_rs::prelude::Typed +pub use rustapi_rs::prelude::TypedPath +pub use rustapi_rs::prelude::TypedPath +pub use rustapi_rs::prelude::UploadedFile +pub use rustapi_rs::prelude::V2Validate +pub use rustapi_rs::prelude::Validatable +pub use rustapi_rs::prelude::ValidatedJson +pub use rustapi_rs::prelude::WithStatus +pub use rustapi_rs::prelude::debug +pub use rustapi_rs::prelude::delete +pub use rustapi_rs::prelude::delete_route +pub use rustapi_rs::prelude::error +pub use rustapi_rs::prelude::get +pub use rustapi_rs::prelude::get_route +pub use rustapi_rs::prelude::info +pub use rustapi_rs::prelude::patch +pub use rustapi_rs::prelude::patch_route +pub use rustapi_rs::prelude::post +pub use rustapi_rs::prelude::post_route +pub use rustapi_rs::prelude::put +pub use rustapi_rs::prelude::put_route +pub use rustapi_rs::prelude::route +pub use rustapi_rs::prelude::serve_dir +pub use rustapi_rs::prelude::sse_response +pub use rustapi_rs::prelude::trace +pub use rustapi_rs::prelude::warn +pub mod rustapi_rs::protocol 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/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/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/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/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-core/src/path_validation.rs b/crates/rustapi-core/src/path_validation.rs index 3f2d9afa..7f06fe46 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 }, @@ -135,7 +136,7 @@ impl std::error::Error for PathValidationError {} /// /// # Examples /// -/// ``` +/// ```rust,ignore /// use rustapi_core::path_validation::validate_path; /// /// // Valid paths @@ -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/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/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/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 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"] } ```