Skip to content

[rust-salvo] Add Rust server generator targeting the Salvo web framework#23772

Open
AuroraMaster wants to merge 8 commits into
OpenAPITools:masterfrom
AuroraMaster:add-rust-salvo-server-generator
Open

[rust-salvo] Add Rust server generator targeting the Salvo web framework#23772
AuroraMaster wants to merge 8 commits into
OpenAPITools:masterfrom
AuroraMaster:add-rust-salvo-server-generator

Conversation

@AuroraMaster
Copy link
Copy Markdown

@AuroraMaster AuroraMaster commented May 12, 2026

What this PR does

Adds a new rust-salvo server generator that mirrors the rust-axum baseline (security, schema composition, parameter / wire format coverage) but targets the Salvo web framework.

Closes #23771.

Highlights

  • Handlers use #[salvo::oapi::endpoint] plus the typed extractors from salvo::oapi::extract (JsonBody<T>, PathParam<T>, QueryParam<T, REQUIRED>, HeaderParam<T, REQUIRED>, FormBody<T>).
  • Models derive salvo::oapi::ToSchema so they flow through the extractors and surface in the generated OpenAPI doc.
  • Routes use the modern {name} path syntax (Salvo >= 0.76), driven by a salvoRoutes bundle populated in postProcessSupportingFileData.
  • Auth middleware (ApiKeyAuth / BasicAuth / BearerAuth) is scaffolded from the spec's securitySchemes when enableAuthMiddleware=true. CORS and request validation are gated by enableCorsMiddleware / enableRequestValidation.
  • Cargo deps pinned to current stable: salvo 0.93, validator 0.20, base64 0.22.

Validation

  • RustSalvoServerCodegenTest: 7/7 pass.
  • Generated petstore (bin/configs/manual/rust-salvo-petstore.yaml) and all three test specs compile with cargo check cleanly on Salvo 0.93 (0 errors, 0 warnings).
  • docs/generators/rust-salvo.md auto-exported via bin/utils/export_generator.sh; declared feature set matches rust-axum (allOf / anyOf / oneOf, JSON, ApiKey + Basic + Bearer).
  • Forbiddenapis check passes locally on modules/openapi-generator.

cc Rust technical committee: @frol @farcaller @richardwhiuk @paladinzh @jacob-pro @dsteeley

PR checklist


Summary by cubic

Adds a new rust-salvo server generator that targets the Salvo web framework with parity to rust-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

    • Generates handlers, models, routes, and optional middleware for Salvo 0.93.
    • Uses #[salvo::oapi::endpoint] with extractors like JsonBody<T>, PathParam<T>, QueryParam<T, REQUIRED>, HeaderParam<T, REQUIRED>, FormBody<T>.
    • Models derive salvo::oapi::ToSchema; paths keep modern {name} syntax.
    • Flags: enableAuthMiddleware, enableCorsMiddleware, enableRequestValidation, enableResponseValidation.
    • Feature parity with rust-axum: JSON, ApiKey/Basic/Bearer, allOf/anyOf/oneOf.
    • Registered in SPI; generator docs added; tests cover basic/auth/validation; petstore sample builds (cargo check clean).
  • Dependencies

    • salvo 0.93 (features: oapi, cors, jwt-auth, serve-static).
    • validator 0.20 (enabled when request validation is on).
    • base64 0.22, tokio 1, async-trait, chrono, serde, serde_json, tracing, tracing-subscriber.
    • Optional uuid 1 when UUIDs are present.

Written for commit 496f50d. Summary will update on new commits.

root added 8 commits May 12, 2026 16:51
- 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.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
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}}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REQ] [rust-salvo] New server generator targeting the Salvo web framework

2 participants