Skip to content

Commit e488d1f

Browse files
author
root
committed
[rust-salvo] Preserve OAuth2 scopes in endpoint security annotation
Pull the declared scopes from `CodegenSecurity.scopes` onto each Salvo auth scheme descriptor and render them inside the `#[salvo::oapi::endpoint(security(...))]` array. Previously every scheme was emitted with an empty `[]`, dropping the OAuth2 / OIDC authorization scopes from the OpenAPI source spec (e.g. `("OAuth2" = [])` → `("OAuth2" = ["write:pets", "read:pets"])`).
1 parent 77df781 commit e488d1f

4 files changed

Lines changed: 69 additions & 18 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustSalvoServerCodegen.java

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -472,38 +472,68 @@ static class SalvoRouteGroup {
472472

473473
// Auth scheme descriptor exposed to templates. `kind` is used to pick the
474474
// factory function in routes.mustache; `schemaName` shows up in the
475-
// `#[salvo::oapi::endpoint(security(...))]` annotation.
475+
// `#[salvo::oapi::endpoint(security(...))]` annotation along with any
476+
// declared OAuth2/OIDC scopes.
476477
static class SalvoAuthScheme {
477-
public String kind; // "apiKey" | "basic" | "bearer"
478-
public String factory; // factory fn in middleware.rs
478+
public String kind; // "apiKey" | "basic" | "bearer" | "oauth2"
479+
public String factory; // factory fn in middleware.rs, null for oauth2
479480
public String schemaName; // name used in OpenAPI security() annotation
481+
public List<SalvoAuthScope> scopes;
480482

481-
SalvoAuthScheme(String kind, String factory, String schemaName) {
483+
SalvoAuthScheme(String kind, String factory, String schemaName, List<SalvoAuthScope> scopes) {
482484
this.kind = kind;
483485
this.factory = factory;
484486
this.schemaName = schemaName;
487+
this.scopes = scopes;
485488
}
486489

487490
static SalvoAuthScheme fromCodegen(CodegenSecurity sec) {
491+
List<SalvoAuthScope> scopeList = extractScopes(sec);
488492
if (Boolean.TRUE.equals(sec.isApiKey)) {
489-
return new SalvoAuthScheme("apiKey", "api_key_auth", "ApiKeyAuth");
493+
return new SalvoAuthScheme("apiKey", "api_key_auth", "ApiKeyAuth", scopeList);
490494
}
491495
if (Boolean.TRUE.equals(sec.isBasic) && Boolean.TRUE.equals(sec.isBasicBearer)) {
492-
return new SalvoAuthScheme("bearer", "bearer_auth", "BearerAuth");
496+
return new SalvoAuthScheme("bearer", "bearer_auth", "BearerAuth", scopeList);
493497
}
494498
if (Boolean.TRUE.equals(sec.isBasic) && Boolean.TRUE.equals(sec.isBasicBasic)) {
495-
return new SalvoAuthScheme("basic", "basic_auth_hoop", "BasicAuth");
499+
return new SalvoAuthScheme("basic", "basic_auth_hoop", "BasicAuth", scopeList);
496500
}
497501
if (Boolean.TRUE.equals(sec.isBasic)) {
498-
return new SalvoAuthScheme("basic", "basic_auth_hoop", "BasicAuth");
502+
return new SalvoAuthScheme("basic", "basic_auth_hoop", "BasicAuth", scopeList);
499503
}
500504
// OAuth2 / OpenIdConnect: leave runtime enforcement to the user,
501-
// but still surface the scheme so the OpenAPI annotation is correct.
505+
// but surface the scheme + scopes so the OpenAPI annotation stays
506+
// faithful to the source spec.
502507
if (Boolean.TRUE.equals(sec.isOAuth)) {
503-
return new SalvoAuthScheme("oauth2", null, "OAuth2");
508+
return new SalvoAuthScheme("oauth2", null, "OAuth2", scopeList);
504509
}
505510
return null;
506511
}
512+
513+
@SuppressWarnings("unchecked")
514+
private static List<SalvoAuthScope> extractScopes(CodegenSecurity sec) {
515+
List<SalvoAuthScope> out = new ArrayList<>();
516+
if (sec.scopes == null) {
517+
return out;
518+
}
519+
for (Map<String, Object> entry : sec.scopes) {
520+
Object name = entry.get("scope");
521+
if (name != null) {
522+
out.add(new SalvoAuthScope(name.toString()));
523+
}
524+
}
525+
return out;
526+
}
527+
}
528+
529+
// Single OAuth2/OIDC scope name. Held in its own class so mustache can
530+
// render the comma-separated list naturally with `{{#-last}}` semantics.
531+
static class SalvoAuthScope {
532+
public String scope;
533+
534+
SalvoAuthScope(String scope) {
535+
this.scope = scope;
536+
}
507537
}
508538

509539
@Override

modules/openapi-generator/src/main/resources/rust-salvo/handlers.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::models::{self, *};
2727
#[salvo::oapi::endpoint(
2828
operation_id = "{{operationId}}",
2929
tags("{{classname}}"){{#vendorExtensions.x-has-auth}},
30-
security({{#vendorExtensions.x-salvo-auth-schemes}}("{{schemaName}}" = []){{^-last}}, {{/-last}}{{/vendorExtensions.x-salvo-auth-schemes}}){{/vendorExtensions.x-has-auth}}
30+
security({{#vendorExtensions.x-salvo-auth-schemes}}("{{schemaName}}" = [{{#scopes}}"{{scope}}"{{^-last}}, {{/-last}}{{/scopes}}]){{^-last}}, {{/-last}}{{/vendorExtensions.x-salvo-auth-schemes}}){{/vendorExtensions.x-has-auth}}
3131
)]
3232
pub async fn {{operationId}}(
3333
{{#hasParams}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustSalvoServerCodegenTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,27 @@ public void testMiddlewareGeneration() throws IOException {
137137
TestUtils.assertFileNotContains(libPath, ".hoop(auth_middleware())");
138138
}
139139

140+
@Test
141+
public void testOAuth2ScopesPropagateToEndpointAnnotation() throws IOException {
142+
Path target = Files.createTempDirectory("salvo-oauth-scopes-test");
143+
final CodegenConfigurator configurator = new CodegenConfigurator()
144+
.setGeneratorName("rust-salvo")
145+
.setInputSpec("src/test/resources/3_0/petstore.yaml")
146+
.setSkipOverwrite(false)
147+
.setOutputDir(target.toAbsolutePath().toString().replace("\\", "/"));
148+
149+
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
150+
files.forEach(File::deleteOnExit);
151+
152+
// Petstore's `petstore_auth` OAuth2 scheme grants `write:pets` and
153+
// `read:pets`; the generated endpoint annotation must carry those
154+
// exact scopes rather than an empty list.
155+
Path petHandlersPath = Path.of(target.toString(), "src/handlers/pet.rs");
156+
TestUtils.assertFileExists(petHandlersPath);
157+
TestUtils.assertFileContains(petHandlersPath,
158+
"security((\"OAuth2\" = [\"write:pets\", \"read:pets\"]))");
159+
}
160+
140161
@Test
141162
public void testSerdeRenameOnCamelCaseFields() throws IOException {
142163
Path target = Files.createTempDirectory("salvo-rename-test");

samples/server/petstore/rust-salvo/output/petstore/src/handlers/pet.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::models::{self, *};
2424
#[salvo::oapi::endpoint(
2525
operation_id = "add_pet",
2626
tags("pet"),
27-
security(("OAuth2" = []))
27+
security(("OAuth2" = ["write:pets", "read:pets"]))
2828
)]
2929
pub async fn add_pet(
3030
pet: JsonBody<Pet>,
@@ -40,7 +40,7 @@ pub async fn add_pet(
4040
#[salvo::oapi::endpoint(
4141
operation_id = "delete_pet",
4242
tags("pet"),
43-
security(("OAuth2" = []))
43+
security(("OAuth2" = ["write:pets", "read:pets"]))
4444
)]
4545
pub async fn delete_pet(
4646
pet_id: PathParam<i64>,
@@ -57,7 +57,7 @@ pub async fn delete_pet(
5757
#[salvo::oapi::endpoint(
5858
operation_id = "find_pets_by_status",
5959
tags("pet"),
60-
security(("OAuth2" = []))
60+
security(("OAuth2" = ["read:pets"]))
6161
)]
6262
pub async fn find_pets_by_status(
6363
status: QueryParam<Vec<String>, true>,
@@ -73,7 +73,7 @@ pub async fn find_pets_by_status(
7373
#[salvo::oapi::endpoint(
7474
operation_id = "find_pets_by_tags",
7575
tags("pet"),
76-
security(("OAuth2" = []))
76+
security(("OAuth2" = ["read:pets"]))
7777
)]
7878
pub async fn find_pets_by_tags(
7979
tags: QueryParam<Vec<String>, true>,
@@ -105,7 +105,7 @@ pub async fn get_pet_by_id(
105105
#[salvo::oapi::endpoint(
106106
operation_id = "update_pet",
107107
tags("pet"),
108-
security(("OAuth2" = []))
108+
security(("OAuth2" = ["write:pets", "read:pets"]))
109109
)]
110110
pub async fn update_pet(
111111
pet: JsonBody<Pet>,
@@ -121,7 +121,7 @@ pub async fn update_pet(
121121
#[salvo::oapi::endpoint(
122122
operation_id = "update_pet_with_form",
123123
tags("pet"),
124-
security(("OAuth2" = []))
124+
security(("OAuth2" = ["write:pets", "read:pets"]))
125125
)]
126126
pub async fn update_pet_with_form(
127127
pet_id: PathParam<i64>,
@@ -139,7 +139,7 @@ pub async fn update_pet_with_form(
139139
#[salvo::oapi::endpoint(
140140
operation_id = "upload_file",
141141
tags("pet"),
142-
security(("OAuth2" = []))
142+
security(("OAuth2" = ["write:pets", "read:pets"]))
143143
)]
144144
pub async fn upload_file(
145145
pet_id: PathParam<i64>,

0 commit comments

Comments
 (0)