[rust-salvo] Add Rust server generator targeting the Salvo web framework#23772
[rust-salvo] Add Rust server generator targeting the Salvo web framework#23772AuroraMaster wants to merge 8 commits into
Conversation
- Add RustSalvoServerCodegen.java with traditional MVC architecture - Support for Salvo web framework with async handlers - Include request validation, auth middleware, and CORS support - Generate handlers, models, routes, and middleware modules - Follow OpenAPI Generator conventions for consistency
- Register RustSalvoServerCodegen in SPI configuration - Add RustSalvoServerCodegenTest with comprehensive test cases - Create test OpenAPI specs for basic, auth, and validation scenarios - Test file generation, dependency injection, and middleware support - Verify Cargo.toml, handlers, routes, and validation features
Bring rust-salvo up to current Salvo idioms after the long rebase against
upstream master. The generator was previously pinned to Salvo 0.73 with
deprecated `<>` path syntax, a non-existent `salvo-cors` crate, a broken
`lambdaVersion` lambda reference, and a base64 0.21 API that no longer
compiles on the 0.22 line.
Highlights:
- Cargo.mustache: salvo 0.73 -> 0.93, validator 0.18 -> 0.20, drop the
fake `salvo-cors` dependency (cors is a salvo feature), pin tokio to
the macros/rt-multi-thread/signal subset, add base64/async-trait.
- Codegen: stop converting OpenAPI `{id}` paths to the deprecated `<id>`
syntax; Salvo 0.76+ matches OpenAPI directly. Push boolean option
defaults into additionalProperties so templates and tests can rely on
them.
- handlers.mustache: rewrite to use `salvo::oapi::extract::{JsonBody,
PathParam, QueryParam, HeaderParam, FormBody}` instead of mixing the
oapi endpoint macro with the legacy `#[salvo(extract(...))]` attribute.
Triple-stash `dataType` so generics like `Vec<String>` aren't
HTML-escaped.
- handlers-mod.mustache: iterate `apis` by tag (one `pub mod` per API
group) instead of iterating operations, which produced duplicates.
- middleware.mustache: switch base64 to the 0.22 `Engine` API and stop
bubbling a `Result` out of a `()`-returning handler.
- models.mustache: derive `salvo::oapi::ToSchema` so generated models can
flow through `JsonBody<T>` and friends. Triple-stash `dataType`.
- Add `bin/configs/manual/rust-salvo-petstore.yaml` so samples can be
regenerated through the standard flow.
- Tests: align fixtures with the new output (salvo 0.93, validator 0.20,
modern auth struct names, per-tag handler module names).
Known follow-ups: a full petstore generation still has ~21 remaining
`cargo check` errors around inline-enum duplication and ToSchema
coverage; those require model-template work to reach axum-level parity.
- models.mustache: drop the inline-enum block that emitted empty
`pub enum Status {}` for each model with an enum-valued field. The
variant set was never populated (the codegen still types these as
`Option<String>`), so the empty enums only produced E0428 duplicate
definitions across models that happened to share a field name.
- models.mustache: `use crate::models;` so `models::Foo` cross-references
emitted by the codegen resolve while we are already inside the models
module (same trick rust-axum uses).
- handlers.mustache: replace `use crate::models::*;` with
`use crate::models::{self, *};` so handler files also see the `models`
module name (needed for `JsonBody<Vec<models::User>>` and similar).
- models.mustache: merge the two `#[derive(...)]` lines, remove now-dead
`hasEnums` block.
Verified with the bundled petstore.yaml plus all three rust-salvo test
specs — every generated project passes `cargo check` cleanly against
salvo 0.93. The Java test suite (7 tests) still passes.
- RustSalvoServerCodegen: claim allOf and anyOf alongside oneOf in schemaSupportFeatures so the generator advertises the same composite schema surface as rust-axum (now ✓ in the feature matrix). - Add docs/generators/rust-salvo.md (auto-generated via bin/utils/export_generator.sh), matching the structure of rust-axum.md / rust-server.md. - README.mustache: rewrite to reflect what the generator actually emits on Salvo 0.93 — the salvo::oapi::endpoint macro, the typed extractors from salvo::oapi::extract, ToSchema-derived models, the routes / handlers / middleware module layout, and re-generation flags for optional auth / CORS / validation. Drops stale wording about Salvo 1.70 and the previous middleware shape. - CLAUDE.md: replace the "in-progress" stub with an accurate snapshot of the rust-salvo generator's HEAD state and remaining axum-parity gaps.
CI build failed forbiddenapis check at RustSalvoServerCodegen.java:332 and :337 because String#toLowerCase() relies on the JVM default locale and can produce wrong results under, e.g., the Turkish locale. Switch to toLowerCase(Locale.ROOT) and lift the value into a local so both call sites share the locale-safe form. Local `./mvnw verify` on modules/openapi-generator now passes the forbiddenapis scan with 0 errors, and the 7 RustSalvoServerCodegenTest cases still pass.
CLAUDE.md is fork-local development guidance (kept on the fork's master) and should not be part of the upstream PR.
Output of bin/configs/manual/rust-salvo-petstore.yaml. Verified with cargo check (clean) against Salvo 0.93.
There was a problem hiding this comment.
6 issues found across 34 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="modules/openapi-generator/src/main/resources/rust-salvo/handlers.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/rust-salvo/handlers.mustache:30">
P2: Multiple auth schemes are collapsed into one security requirement, so alternative auth methods become combined (AND) instead of remaining alternatives (OR).</violation>
</file>
<file name="modules/openapi-generator/src/test/resources/3_0/rust/rust-salvo-auth-test.yaml">
<violation number="1" location="modules/openapi-generator/src/test/resources/3_0/rust/rust-salvo-auth-test.yaml:64">
P2: Auth fixture omits BasicAuth, so the rust-salvo middleware test never exercises the basic-auth path it expects.</violation>
</file>
<file name="modules/openapi-generator/src/main/resources/rust-salvo/lib.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/rust-salvo/lib.mustache:48">
P1: Auth is installed as a global service hoop, so it runs on every request and can block public operations because the generated middleware is not operation-aware.</violation>
</file>
<file name="samples/server/petstore/rust-salvo/output/petstore/src/models.rs">
<violation number="1" location="samples/server/petstore/rust-salvo/output/petstore/src/models.rs:72">
P1: Missing serde rename attributes cause JSON wire names to drift from OpenAPI camelCase to Rust snake_case, breaking API contract interoperability. For example, `pet_id` serializes as `pet_id` instead of `petId`, `ship_date` instead of `shipDate`, and `photo_urls` instead of `photoUrls`. The codegen templates should emit `#[serde(rename = "...")]` (or a struct-level `#[serde(rename_all = "camelCase")]` if all fields follow that convention) to preserve the original OpenAPI field names.</violation>
</file>
<file name="modules/openapi-generator/src/main/resources/rust-salvo/middleware.mustache">
<violation number="1" location="modules/openapi-generator/src/main/resources/rust-salvo/middleware.mustache:78">
P2: Authentication scheme parsing is case-sensitive, so valid Basic/Bearer `Authorization` headers with different casing will be rejected.</violation>
</file>
<file name="samples/server/petstore/rust-salvo/output/petstore/README.md">
<violation number="1" location="samples/server/petstore/rust-salvo/output/petstore/README.md:65">
P3: The generated README leaves the API endpoints section empty instead of listing the server routes.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
| .hoop(salvo::cors::Cors::new()) | ||
| {{/enableCorsMiddleware}} | ||
| {{#enableAuthMiddleware}} | ||
| .hoop(auth_middleware()) |
There was a problem hiding this comment.
P1: Auth is installed as a global service hoop, so it runs on every request and can block public operations because the generated middleware is not operation-aware.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/rust-salvo/lib.mustache, line 48:
<comment>Auth is installed as a global service hoop, so it runs on every request and can block public operations because the generated middleware is not operation-aware.</comment>
<file context>
@@ -0,0 +1,55 @@
+ .hoop(salvo::cors::Cors::new())
+ {{/enableCorsMiddleware}}
+ {{#enableAuthMiddleware}}
+ .hoop(auth_middleware())
+ {{/enableAuthMiddleware}}
+}
</file context>
| #[serde(skip_serializing_if = "Option::is_none")] | ||
| pub id: Option<i64>, | ||
| #[serde(skip_serializing_if = "Option::is_none")] | ||
| pub pet_id: Option<i64>, |
There was a problem hiding this comment.
P1: Missing serde rename attributes cause JSON wire names to drift from OpenAPI camelCase to Rust snake_case, breaking API contract interoperability. For example, pet_id serializes as pet_id instead of petId, ship_date instead of shipDate, and photo_urls instead of photoUrls. The codegen templates should emit #[serde(rename = "...")] (or a struct-level #[serde(rename_all = "camelCase")] if all fields follow that convention) to preserve the original OpenAPI field names.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/server/petstore/rust-salvo/output/petstore/src/models.rs, line 72:
<comment>Missing serde rename attributes cause JSON wire names to drift from OpenAPI camelCase to Rust snake_case, breaking API contract interoperability. For example, `pet_id` serializes as `pet_id` instead of `petId`, `ship_date` instead of `shipDate`, and `photo_urls` instead of `photoUrls`. The codegen templates should emit `#[serde(rename = "...")]` (or a struct-level `#[serde(rename_all = "camelCase")]` if all fields follow that convention) to preserve the original OpenAPI field names.</comment>
<file context>
@@ -0,0 +1,180 @@
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub id: Option<i64>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub pet_id: Option<i64>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub quantity: Option<i32>,
</file context>
| #[salvo::oapi::endpoint( | ||
| operation_id = "{{operationId}}", | ||
| tags("{{classname}}"){{#vendorExtensions.x-has-auth}}{{#hasAuthMethods}}, | ||
| security(("{{#authMethods}}{{#isApiKey}}ApiKeyAuth{{/isApiKey}}{{#isBasic}}BasicAuth{{/isBasic}}{{#isBearer}}BearerAuth{{/isBearer}}{{^-last}}", "{{/-last}}{{/authMethods}}")){{/hasAuthMethods}}{{/vendorExtensions.x-has-auth}} |
There was a problem hiding this comment.
P2: Multiple auth schemes are collapsed into one security requirement, so alternative auth methods become combined (AND) instead of remaining alternatives (OR).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/rust-salvo/handlers.mustache, line 30:
<comment>Multiple auth schemes are collapsed into one security requirement, so alternative auth methods become combined (AND) instead of remaining alternatives (OR).</comment>
<file context>
@@ -0,0 +1,66 @@
+#[salvo::oapi::endpoint(
+ operation_id = "{{operationId}}",
+ tags("{{classname}}"){{#vendorExtensions.x-has-auth}}{{#hasAuthMethods}},
+ security(("{{#authMethods}}{{#isApiKey}}ApiKeyAuth{{/isApiKey}}{{#isBasic}}BasicAuth{{/isBasic}}{{#isBearer}}BearerAuth{{/isBearer}}{{^-last}}", "{{/-last}}{{/authMethods}}")){{/hasAuthMethods}}{{/vendorExtensions.x-has-auth}}
+)]
+pub async fn {{operationId}}(
</file context>
| @@ -0,0 +1,72 @@ | |||
| openapi: 3.0.1 | |||
There was a problem hiding this comment.
P2: Auth fixture omits BasicAuth, so the rust-salvo middleware test never exercises the basic-auth path it expects.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/resources/3_0/rust/rust-salvo-auth-test.yaml, line 64:
<comment>Auth fixture omits BasicAuth, so the rust-salvo middleware test never exercises the basic-auth path it expects.</comment>
<file context>
@@ -0,0 +1,72 @@
+ format: int32
+ message:
+ type: string
+ securitySchemes:
+ ApiKeyAuth:
+ type: apiKey
</file context>
| openapi: 3.0.1 | |
| securitySchemes: | |
| ApiKeyAuth: | |
| type: apiKey | |
| in: header | |
| name: X-API-Key | |
| BasicAuth: | |
| type: http | |
| scheme: basic | |
| BearerAuth: | |
| type: http | |
| scheme: bearer | |
| bearerFormat: JWT |
| @@ -0,0 +1,165 @@ | |||
| {{>partial_header}} | |||
There was a problem hiding this comment.
P2: Authentication scheme parsing is case-sensitive, so valid Basic/Bearer Authorization headers with different casing will be rejected.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/rust-salvo/middleware.mustache, line 78:
<comment>Authentication scheme parsing is case-sensitive, so valid Basic/Bearer `Authorization` headers with different casing will be rejected.</comment>
<file context>
@@ -0,0 +1,165 @@
+ if let Some(auth_header) = req.headers().get("authorization")
+ .and_then(|v| v.to_str().ok()) {
+
+ if auth_header.starts_with("Basic ") {
+ let encoded = &auth_header[6..];
+ if let Ok(decoded) = BASE64_STANDARD.decode(encoded) {
</file context>
|
|
||
| `tracing-subscriber` honours `RUST_LOG`, so set it to whatever level you need (`error`, `warn`, `info`, `debug`, `trace`). | ||
|
|
||
| ## API endpoints |
There was a problem hiding this comment.
P3: The generated README leaves the API endpoints section empty instead of listing the server routes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/server/petstore/rust-salvo/output/petstore/README.md, line 65:
<comment>The generated README leaves the API endpoints section empty instead of listing the server routes.</comment>
<file context>
@@ -0,0 +1,86 @@
+
+`tracing-subscriber` honours `RUST_LOG`, so set it to whatever level you need (`error`, `warn`, `info`, `debug`, `trace`).
+
+## API endpoints
+
+
</file context>
| @@ -0,0 +1,11 @@ | |||
| generatorName: rust-salvo | |||
| outputDir: samples/server/petstore/rust-salvo/output/petstore | |||
There was a problem hiding this comment.
please add the new sample folder to the workflow: https://github.com/OpenAPITools/openapi-generator/blob/master/.github/workflows/samples-rust-server.yaml so that CI will test it moving forward
What this PR does
Adds a new
rust-salvoserver generator that mirrors the rust-axum baseline (security, schema composition, parameter / wire format coverage) but targets the Salvo web framework.Closes #23771.
Highlights
#[salvo::oapi::endpoint]plus the typed extractors fromsalvo::oapi::extract(JsonBody<T>,PathParam<T>,QueryParam<T, REQUIRED>,HeaderParam<T, REQUIRED>,FormBody<T>).salvo::oapi::ToSchemaso they flow through the extractors and surface in the generated OpenAPI doc.{name}path syntax (Salvo >= 0.76), driven by asalvoRoutesbundle populated inpostProcessSupportingFileData.ApiKeyAuth/BasicAuth/BearerAuth) is scaffolded from the spec'ssecuritySchemeswhenenableAuthMiddleware=true. CORS and request validation are gated byenableCorsMiddleware/enableRequestValidation.Validation
RustSalvoServerCodegenTest: 7/7 pass.bin/configs/manual/rust-salvo-petstore.yaml) and all three test specs compile withcargo checkcleanly on Salvo 0.93 (0 errors, 0 warnings).docs/generators/rust-salvo.mdauto-exported viabin/utils/export_generator.sh; declared feature set matchesrust-axum(allOf / anyOf / oneOf, JSON, ApiKey + Basic + Bearer).modules/openapi-generator.cc Rust technical committee: @frol @farcaller @richardwhiuk @paladinzh @jacob-pro @dsteeley
PR checklist
./mvnw clean package,./bin/generate-samples.sh ./bin/configs/manual/rust-salvo-petstore.yaml, and./bin/utils/export_generator.sh rust-salvoall run cleanly; generated artifacts are committed.master.Summary by cubic
Adds a new
rust-salvoserver generator that targets the Salvo web framework with parity torust-axum. It outputs Salvo 0.93 projects with handlers, models, routes, and optional auth/CORS/validation middleware, plus docs, tests, and a petstore sample.New Features
handlers,models,routes, and optionalmiddlewarefor Salvo 0.93.#[salvo::oapi::endpoint]with extractors likeJsonBody<T>,PathParam<T>,QueryParam<T, REQUIRED>,HeaderParam<T, REQUIRED>,FormBody<T>.salvo::oapi::ToSchema; paths keep modern{name}syntax.enableAuthMiddleware,enableCorsMiddleware,enableRequestValidation,enableResponseValidation.rust-axum: JSON, ApiKey/Basic/Bearer, allOf/anyOf/oneOf.cargo checkclean).Dependencies
salvo0.93(features:oapi,cors,jwt-auth,serve-static).validator0.20(enabled when request validation is on).base640.22,tokio1,async-trait,chrono,serde,serde_json,tracing,tracing-subscriber.uuid1when UUIDs are present.Written for commit 496f50d. Summary will update on new commits.