Keep same URL but contract changes in Spring Boot REST Open API 3? - spring

I am using Spring Boot and REST and Open API 3 implementation. In this example, v1 Group has List implementation - all data will get in List, in v2 Group has pagination implementation - all data will come in the form of pages.
For the consumer, we don't want to change endpoint url for them to be consume.
Endpoint which returns list.
#GetMapping(value = "/contacts", headers = {"Accept-version=v1"})
public ResponseEntity<List<Contact>> findAll() {
List<Contact> contacts = contactService.findContactList();
return new ResponseEntity<>(contacts, HttpStatus.OK);
}
Endpoint with Pagination
#GetMapping(value = "/contacts", headers = {"Accept-version=v2"})
public ResponseEntity<List<Contact>> findAll(Pageable pageable) {
Page<Contact> contactPages = contactService.findContactPageable(pageable);
return new ResponseEntity<>(contactPages, HttpStatus.OK);
}
I want V1 endpoint to be shown in GroupedOpenApi and v2 endpoint to be shown in the GroupedOpenApi2. Any suggestions ?

Lets assume you put the two endpoints in different packaged and then use the Following GroupedOpenApi definition:
#Bean
public GroupedOpenApi groupOpenApiV1() {
return GroupedOpenApi.builder()
.setGroup("v1")
.packagesToScan("test.org.springdoc.api.v1")
.build();
}
#Bean
public GroupedOpenApi groupOpenApiV2() {
return GroupedOpenApi.builder()
.setGroup("v2")
.packagesToScan("test.org.springdoc.api.v2")
.build();
}

Related

Swagger overwrites methods with the same path and method but different parameters

Swagger overwrites methods with the same path and method but different parameters
I have an application with Spring Boot 2.3.5.RELEASE, webflux and springfox 3.0.0. I have developed two GET methods with the same path but different parameters, one does not receive parameters and returns a list and others for findAll.
The case is that Swagger only generates the documentation of one of the methods, sometimes the listing, others the paging. How can I tell swagger that they are different methods and document both for me?
My Controller code:
#GetMapping(value = "/foo", params = {"page", "size"})
#ResponseBody
public Mono<ResponseEntity<Mono<Page<FooDTO>>>> findByFilter(FooFilterDTO filter,
#SortDefault(sort = "id", direction = Sort.Direction.DESC) #PageableDefault(value = 10) Pageable pageable) {
//...
}
#GetMapping(value = "/foo")
#ResponseBody
public Mono<ResponseEntity<Flux<FooDTO>>> findAll() {
//...
}
My Swagger configuration:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Value("${app.version}")
private String version;
#Bean
public Docket docketUsersV1() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(this.fooApiInfo())
.enable(true)
.groupName("foo-api")
.securityContexts(Arrays.asList(securityContext()))
.securitySchemes(Arrays.asList(apiKey()))
.select()
.paths(fooPaths())
.build();
}
private ApiInfo fooApiInfo() {
return new ApiInfoBuilder()
.title("Reactive Foo")
.description("Reactive API")
.version(appVersion)
.build();
}
private Predicate<String> fooPaths() {
return regex("/foo.*");
}
}
As far as I know, you can only define one API path by each HTTP verb (GET, POST...), independently of the optional parameters the API consumer sends.
My advice would be to define a single GET /foo path, with optional parameters page & size (ie. not required)
Then I would have a single entrypoint function in the controller, then redirect to each findByFilter private method or findAll private method depending on whether page & size are defined or not.

How can we change the default Swagger URL to any other

I have integrated Swagger into my Spring Boot Application. I am able to view the documentation created for a single Controller class , on the default swagger URL i.e - http://localhost:8081/swagger-ui.html#/
How can we change '/swagger-ui.html/' path to any other Custom path .
Also adding the code snippet. I want the url to be like :
http://localhost:8081/swagger#/
#Bean
public Docket usersApi(ServletContext servletContext){
return new Docket(DocumentationType.SWAGGER_2).pathProvider(new RelativePathProvider(servletContext){
#Override
public String getApplicationBasePath() {
return "/swagger" + super.getApplicationBasePath();
}
})
.apiInfo(apiInfo())
.select()
.paths(PathSelectors.regex("/api/v1/.*" ))
.build();
}
Thanks in Advance

Limiting Access to Endpoints by Method in Spring Boot

We are implementing a simple health check API our load balancers can call to help with the routing of requests. If the status of the application is "standby", requests should not be sent to it. Only the admins can set the state to "up" or "standby", but anyone (including the load balancers) can get the status of the application.
We are trying this with Spring Boot 2, but are having problems configuring security to grant anonymous access to just one of the routes. Consider the following controller:
#RestController
public class AppStatusController {
private static final String STATUS = "status";
String state = "standby";
private String getState() {
return state;
}
private Map<String, String> getStatusMap() {
Map<String, String> retval = new HashMap<>();
retval.put(STATUS, getState());
return retval;
}
// GET calls are public, all others require AuthN & AuthZ
#GetMapping(path = "/appstatus", produces = "application/json")
public Map<String, String> getStatus() {
return getStatusMap();
}
// Only those with the ADMIN role can POST to this endpoint
#PostMapping(path = "/appstatus", consumes = "application/json", produces = "application/json")
public Map<String, String> setStatus(#RequestBody Map state) {
// Validate and update the state
return getStatusMap();
}
}
There is only one endpoint, /appstatus, but one method is called with an HTTP GET and the other with an HTTP POST. We want calls to getStatus to be public, but allow Spring Security to control access to setStatus. One might expect an annotation such as #Anonymous or something similar to be applied to the getStatus() method but we can't seem to find one.
Some have suggested using a separate #Configuration class and setting up antMatchers but it's not clear how we can match on the HTTP method.
Does anyone have suggestions on how to configure Spring Security to allow public access to GET method requests but control access to other methods?
EDIT: We are trying to avoid any authentication on the getStatus() call. We can't store auth credentials in the health check probe and can't perform a login exchange. This is a simple GET request to see if the application is up and ready for operation.
Have you tried using Method Security Expressions?
It looks like this will do what you want:
// GET calls are public, all others require AuthN & AuthZ
#GetMapping(path = "/appstatus", produces = "application/json")
#PreAuthorize("permitAll")
public Map<String, String> getStatus() {
return getStatusMap();
}
// Only those with the ADMIN role can POST to this endpoint
#PostMapping(path = "/appstatus", consumes = "application/json", produces = "application/json")
#PreAuthorize("hasRole('ADMIN')")
public Map<String, String> setStatus(#RequestBody Map state) {
// Validate and update the state
return getStatusMap();
}
Note: I don't know what roles your admins have, so I used 'ADMIN' as a placeholder.

Rest api filtering in spring boot

I have a project in spring boot, in my controller I have many methods with similar functionality.
Methods for searching post, popular, latest etc and the urls with slight variation like -
url 1 - search/{topicId}
url 2 - search/popular/{topicId}
url 3 - search/latest/{topicId}
What I want, is to have a single method with filter in url like search/{topicId}?filter=popular
How to achieve this in spring boot?
OOPs... it does not depend on SpringBoot. It is simply a URL mapping...You can accept the type as a request param and can process as per business.....
#Controller
public class BookController {
#GetMapping(value = "/search/{topicId}")
#ResponseBody
public List<Object> getBooksByType(#RequestParam String type) {
try{
if("popular".equalsIgnoreCase(type)){
//do your business stuffs
}else if ("latest".equalsIgnoreCase(type)){
//do your business stuffs
}
}catch(Exception e){
e.printStackTrace();
}
return new ArrayList<>();
}
}

Rest API version header in SwaggerUI

I want to use Springfox SwaggerUI for my Rest API (spring-mvc) documentation.
I use version header in #RequestMapping annotation, but if I have two versions of same method, in SwaggerUI I can see only one.
For example:
#GetMapping(value = "/users", headers = "X-API-VERSION=1")
public List<User> getUsersV1(){...}
#GetMapping(value = "/users", headers = "X-API-VERSION=2")
public List<User> getUsersV2(){...}
Above code results in only one method visible in api documentation.
Is there any option to configure Swagger to differ endpoints with consideration of my version header?
After some research I have found solution to my problem, maybe it will help someone in the future. I add "#v" suffix to path using PathDecorator.
Now I can see all my methods in generated documentation.
#Component
#Order(value = Ordered.HIGHEST_PRECEDENCE + 70)
public class VersionPathDecorator implements PathDecorator {
private final static Logger logger = LoggerFactory.getLogger(VersionPathDecorator.class);
#Override
public Function<String, String> decorator(PathContext context) {
return (path) -> {
StringBuilder sb = new StringBuilder(path);
Field parent = null;
try {
parent = PathContext.class.getDeclaredField("parent");
parent.setAccessible(true);
RequestMappingContext rmc = (RequestMappingContext) parent.get(context);
rmc.headers()
.stream()
.filter(h -> RequestHeader.X_API_VERSION.headerName.equals(h.getName()))
.map(NameValueExpression::getValue)
.findFirst()
.ifPresent(v -> sb.append("#v").append(v));
} catch (NoSuchFieldException | IllegalAccessException e) {
logger.error("path decoration failed", e);
}
return sb.toString();
};
}
#Override
public boolean supports(DocumentationContext documentationContext) {
return true;
}
}
Swagger identifies services by its endpoint.
Each feature must respond to a different endpoint, and headers for that function should not be used.
If you are using REST services read a bit about Restfull and follow its principles. This url can help you: http://docs.oracle.com/javaee/6/tutorial/doc/gijqy.html

Resources