From 021ca7dae2418544f401575a2982d6922557cc5c Mon Sep 17 00:00:00 2001 From: TiPo4u4eK <51709419+TiPo4u4eK@users.noreply.github.com> Date: Fri, 31 Oct 2025 20:27:21 +0300 Subject: [PATCH] feat: resources --- .../core/visitor/RouterFunctionVisitor.java | 19 ++- .../api/v31/app193/SpringDocApp193Test.java | 113 ++++++++++++++++++ .../src/test/resources/icons/icon.svg | 3 + .../test/resources/results/3.1.0/app193.json | 40 +++++++ 4 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 springdoc-openapi-starter-webflux-api/src/test/java/test/org/springdoc/api/v31/app193/SpringDocApp193Test.java create mode 100644 springdoc-openapi-starter-webflux-api/src/test/resources/icons/icon.svg create mode 100644 springdoc-openapi-starter-webflux-api/src/test/resources/results/3.1.0/app193.json diff --git a/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/visitor/RouterFunctionVisitor.java b/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/visitor/RouterFunctionVisitor.java index 25ba6e58b..62dab946b 100644 --- a/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/visitor/RouterFunctionVisitor.java +++ b/springdoc-openapi-starter-webflux-api/src/main/java/org/springdoc/webflux/core/visitor/RouterFunctionVisitor.java @@ -26,19 +26,24 @@ package org.springdoc.webflux.core.visitor; +import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Set; import java.util.function.Function; import org.springdoc.core.fn.AbstractRouterFunctionVisitor; import reactor.core.publisher.Mono; import org.springframework.core.io.Resource; +import org.springframework.http.HttpMethod; import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.RequestPredicate; import org.springframework.web.reactive.function.server.RequestPredicates; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.util.ReflectionUtils; /** * The type Router function visitor. @@ -67,7 +72,19 @@ public void endNested(RequestPredicate predicate) { @Override public void resources(Function> lookupFunction) { - // Not yet needed + if ("PathResourceLookupFunction".equals(lookupFunction.getClass().getSimpleName())) { + Field patternField = ReflectionUtils.findField(lookupFunction.getClass(), "pattern"); + if (patternField != null) { + ReflectionUtils.makeAccessible(patternField); + Object patternFieldValue = ReflectionUtils.getField(patternField, lookupFunction); + if (patternFieldValue instanceof PathPattern patternCastedValue) { + this.currentRouterFunctionDatas = new ArrayList<>(); + this.path(patternCastedValue.getPatternString()); + this.commonRoute(); + this.method(Set.of(HttpMethod.GET)); + } + } + } } @Override diff --git a/springdoc-openapi-starter-webflux-api/src/test/java/test/org/springdoc/api/v31/app193/SpringDocApp193Test.java b/springdoc-openapi-starter-webflux-api/src/test/java/test/org/springdoc/api/v31/app193/SpringDocApp193Test.java new file mode 100644 index 000000000..936835e34 --- /dev/null +++ b/springdoc-openapi-starter-webflux-api/src/test/java/test/org/springdoc/api/v31/app193/SpringDocApp193Test.java @@ -0,0 +1,113 @@ +/* + * + * * + * * * + * * * * + * * * * * + * * * * * * Copyright 2019-2025 the original author or authors. + * * * * * * + * * * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * * * you may not use this file except in compliance with the License. + * * * * * * You may obtain a copy of the License at + * * * * * * + * * * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * * * + * * * * * * Unless required by applicable law or agreed to in writing, software + * * * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * * * See the License for the specific language governing permissions and + * * * * * * limitations under the License. + * * * * * + * * * * + * * * + * * + * + */ + +package test.org.springdoc.api.v31.app193; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.junit.jupiter.api.Test; +import org.springdoc.core.annotations.RouterOperation; +import reactor.core.publisher.Mono; +import test.org.springdoc.api.v31.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.reactive.function.server.EntityResponse; +import org.springframework.web.reactive.function.server.HandlerFilterFunction; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class SpringDocApp193Test extends AbstractSpringDocTest { + + @Test + void getIconResource() { + webTestClient.get().uri("/icons/icon.svg").exchange() + .expectStatus().isOk() + .expectHeader().cacheControl(CacheControl.maxAge(Duration.ofHours(24))) + .expectBody().consumeWith(response -> { + byte[] body = response.getResponseBody(); + assertNotNull(body); + String bodyValue = new String(body, StandardCharsets.UTF_8); + assertThat(bodyValue).contains(""); + }); + } + + @Test + void getUnknownResource() { + webTestClient.get().uri("/icons/unknown.svg").exchange() + .expectStatus().isNotFound(); + } + + @SpringBootApplication + @ComponentScan(basePackages = { "org.springdoc" }) + static class SpringDocTestApp { + + @Bean + @RouterOperation( + method = RequestMethod.GET, + operation = @Operation( + operationId = "getIcon", + summary = "Get icons resource.", + responses = @ApiResponse( + responseCode = "200", content = @Content(mediaType = "image/svg+xml"), description = "icon resource", + headers = @Header(name = HttpHeaders.CACHE_CONTROL, required = true, description = "Cache-Control for icons", schema = @Schema(type = "string")) + ) + ) + ) + public RouterFunction getIconsResourceWithCacheControl() { + return RouterFunctions + .resources("/icons/**", new ClassPathResource("icons/")) + .filter(HandlerFilterFunction.ofResponseProcessor(this::injectCacheControlHeader)); + } + + private Mono injectCacheControlHeader(ServerResponse serverResponse) { + if (serverResponse instanceof EntityResponse entityResponse) { + return EntityResponse.fromObject(entityResponse.entity()) + .header(HttpHeaders.CACHE_CONTROL, CacheControl.maxAge(24, TimeUnit.HOURS).getHeaderValue()) + .build().cast(ServerResponse.class); + } + return Mono.just(serverResponse); + } + + } + +} \ No newline at end of file diff --git a/springdoc-openapi-starter-webflux-api/src/test/resources/icons/icon.svg b/springdoc-openapi-starter-webflux-api/src/test/resources/icons/icon.svg new file mode 100644 index 000000000..6d210d9f2 --- /dev/null +++ b/springdoc-openapi-starter-webflux-api/src/test/resources/icons/icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/springdoc-openapi-starter-webflux-api/src/test/resources/results/3.1.0/app193.json b/springdoc-openapi-starter-webflux-api/src/test/resources/results/3.1.0/app193.json new file mode 100644 index 000000000..c738d9efd --- /dev/null +++ b/springdoc-openapi-starter-webflux-api/src/test/resources/results/3.1.0/app193.json @@ -0,0 +1,40 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "", + "description": "Generated server url" + } + ], + "paths": { + "/icons/**": { + "get": { + "operationId": "getIcon", + "summary": "Get icons resource.", + "responses": { + "200": { + "description": "icon resource", + "content": { + "image/svg+xml": {} + }, + "headers": { + "Cache-Control": { + "description": "Cache-Control for icons", + "required": true, + "style":"simple", + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": {} +}