I instatiate docket like this
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.config.internal"))
.paths(Predicates.or(PathSelectors.ant("/api**/**")))
.build();
}
I created a set of stub endpoints that imitate the real one for /login or /oauth.
#Api("Authentication")
#RequestMapping("/api")
public interface LoginEndpointApi {
#ApiOperation(value = "Github SSO endpoint", notes = "Endpoint for Github SSO authentication")
#ApiResponses({
#ApiResponse(code = 200, message = "HTML page of main application")
})
#GetMapping("/oauth/github")
default void oauthGithub() {
throw new UnsupportedOperationException();
}
#ApiOperation(value = "Get CSRF token", notes = "Returns current CSRF token")
#ApiResponses({
#ApiResponse(code = 200, message = "CSRF token response", response = String.class,
examples = #Example({#ExampleProperty(value = "015275eb-293d-4ce9-ba07-ff5e1c348092")}))
})
#GetMapping("/csrf-token")
default void csrfToken() {
throw new UnsupportedOperationException();
}
#ApiOperation(value = "Login endpoint", notes = "Login endpoint for authorization")
#ApiResponses({
#ApiResponse(code = 200, message = "Successful authentication")
})
#PostMapping("/login")
default void login(
#ApiParam(required = true, name = "login", value = "login body")
#RequestBody LoginRequest loginRequest) {
throw new UnsupportedOperationException();
}
}
But it doesn't recognize it. It is located in the same com.config.internal package as I described.
But the page swagger ui is empty and shows that No operations defined in spec!
What is the problem?
If you want to provide swagger documentation for your request mappings specified above you could simply describe it with .paths(Predicates.or(PathSelectors.ant("/api/**"))) path matchers. But if your path includes something more complicated like api + text without backslash separator then you should get known with
https://docs.spring.io/spring/docs/3.1.x/javadoc-api/org/springframework/util/AntPathMatcher.html
Related
I'm using Spring Boot 2.6.7 and Using Open API springdoc-openapi-ui 1.6.4. I have 2 services. From first service I'm using rest template to connect to second service.
In the first service, in rest controller api, I have used HttpEntity to get request object. The same is passed to rest template. The reason is with HttpEntity, I'm passing the request body as well as some other headers as well.
My controller method is as follows.
#PostMapping(value = "/submit", produces = MediaType.APPLICATION_JSON_VALUE)
#Operation(summary = "API for submit", description = "Submit data")
#ApiResponses(value = { #ApiResponse(responseCode = "200", description = "OK"),
#ApiResponse(responseCode = "400", description = "Bad request", content = #Content(schema = #Schema(implementation = Failure.class))),
#ApiResponse(responseCode = "500", description = "Error", content = #Content(schema = #Schema(implementation = Failure.class))), })
public ResponseEntity<Success<SubmitOpr>> submit(HttpEntity<OperationReq> httpEntity) throws Exception {
log.info("Request Entity is {}", httpEntity);
log.info("Request Body is {}", httpEntity.getBody());
SuccessResponse<SubmitOpr> response = null;
try {
response = oprService.submit(httpEntity);
} catch (Exception e) {
log.error("Failure: {}", e.getMessage());
throw e;
}
return ResponseEntity.ok().body(response);
}
My application works fine with this. And with postman client also it works fine.
But when I use swagger UI to test, I did not get expected result. And when I debug, httpEntity.getBody() is null
If I change from HttpEntity<OperationReq> httpEntity to OperationReq httpEntity and then accordingly change subsequent service layer methods, the api works fine in swagger.
But I don't want to change that. Because I want to pass HttpEntity and another thing is there are so many similar APIs and it would be very difficult to change everywhere.
Is there a better solution to this?
I think there is no direct solution to this. If you are looking to get headers along with request body, what you can do is, you can get RequestBody and get Headers in controller and create an HttpEntity object. And that you can pass on to your service layer methods.
Change will be only in controller side and no change will be required from service layers.
#PostMapping(value = "/submit", produces = MediaType.APPLICATION_JSON_VALUE)
#Operation(summary = "API for submit", description = "Submit data")
#ApiResponses(value = { #ApiResponse(responseCode = "200", description = "OK"),
#ApiResponse(responseCode = "400", description = "Bad request", content = #Content(schema = #Schema(implementation = Failure.class))),
#ApiResponse(responseCode = "500", description = "Error", content = #Content(schema = #Schema(implementation = Failure.class))), })
public ResponseEntity<Success<SubmitOpr>> submit(#RequestBody OperationReq operationReq, #RequestHeader MultiValueMap<String, String> headers) throws Exception {
HttpEntity<OperationReq> httpEntity = new HttpEntity<>(operationReq, headers);
log.info("Request Entity is {}", httpEntity);
log.info("Request Body is {}", httpEntity.getBody());
SuccessResponse<SubmitOpr> response = null;
try {
response = oprService.submit(httpEntity);
} catch (Exception e) {
log.error("Failure: {}", e.getMessage());
throw e;
}
return ResponseEntity.ok().body(response);
}
I had the same behavior of Swagger for HttpEntity controller method parameter. In my case the problem was that Swagger sent httpEntity as a query parameter instead body.
I have added a #io.swagger.v3.oas.annotations.parameters.RequestBody annotation and it solved the problem:
#io.swagger.v3.oas.annotations.parameters.RequestBody(
required = true,
description = "Put here your feature configuration DTO in a JSON format",
content = #Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
examples = #ExampleObject(value = "{}")
)
)
HttpEntity<String> httpEntity
I have the following java endpoint inside a Springboot RestController annotated with some Swagger annotations for 4 ApiResponses:
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successfully sign in"),
#ApiResponse(code = 400, message = "Missing request body"),
#ApiResponse(code = 404, message = "Schema not found"),
#ApiResponse(code = 500, message = "Internal error")
})
#PostMapping(
path = "/login",
produces = "application/json; charset=utf-8")
public LoginResponse login(
#ApiParam(
name="cred",
value="Credenciales de quien intenta ingresar al sistema")
#RequestBody CredencialesRequest cred
) throws ControllerException {
return accessService.login(cred.getUsuario(), cred.getClave());
}
As you can see, I have declared 4 response codes as a possible HTTP responses: 200, 400, 404 and 500
When I run the application and go to http://localhost:8080/swagger-ui.html the UI shows the 4 codes that I have described in the endpoint. However, it shows MORE http codes. Please take a look at this picture:
The extra codes are: 201 (created), 401 (unauthorized) & 403 (forbidden). Why? For my use case, the "login" endpoint should be always accessible to any user, so at least, 401 & 403 doesn't make sense at all, in this context.
Just like it was said in the comments, in order to remove the extra http codes in the swagger UI, we need to modify our configuration file by adding useDefaultResponseMessages(false) to the api() method in our SwaggerConfig like this:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false) // I HAD TO ADD THIS LINE !!!!
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
That's it !
I have a controller handler method:
#ApiResponses({
#ApiResponse(code = 201, message = "aaa", response = Response.class),
#ApiResponse(code = 400, message = "bbb")
})
#PostMapping("api/aa")
public Response save() {
...
}
...
Whenever I add a custom Docket configuration as below. #ApiResponse defined in #ApiResponses don't seem to work anymore. As I no longer see them showing up on the UI or under v2/api-docs's json. Any ideas what I am missing?
#Bean
public Docket api() {
return new Docket(DocumentationType.SPRING_WEB)
.host(kongHost)
.useDefaultResponseMessages(false)
.select()
.apis(RequestHandlerSelectors.basePackage("com.mypackage.controller"))
.paths(PathSelectors.ant("/api/*"))
.build();
}
Turns out I would need to specify the DocumentationType to be SWAGGER_2
please i have this error when trying to create a customer. Can some one help me? May be i am missing some thing. I have even try to change the #PostMapping to #RequestMapping till yet. Thks
My Controller code
`#PostMapping("CREATE_CUSTOMER_ENDPOINT")
#ResponseStatus(value = HttpStatus.OK)
#ApiResponses(value = {
#ApiResponse(code = 201, message = "The Customer was Created", response = CustomerDto.class),
#ApiResponse(code = 400, message = "Bad Request", response = ResponseError.class),
#ApiResponse(code = 500, message = "Unexpected error")
})
public ResponseEntity createCustomer(final HttpServletRequest request, #RequestBody CustomerDto customerDto)
{
if (log.isDebugEnabled()){
log.debug("[CustomerResource] POST {} : Creating customer ", CREATE_CUSTOMER_ENDPOINT);
}
if(customerDto.getUidpk()!=null) {
ResponseError error = new ResponseError(HttpStatus.BAD_REQUEST.getReasonPhrase(), "A customer Already exist with an Uidpk");
log.error("[CustomerResource] The customer Already exist ({}) with an Uidpk", customerDto.getUidpk());
return new ResponseEntity<>(error, null, HttpStatus.BAD_REQUEST);
}
CustomerDto result = customerService.createCustomer(customerDto);
log.debug("[CustomerResource] Customer created ({})", result.getUidpk());
return new ResponseEntity<>(result, HeaderUtil.putLocationHeader(request.getRequestURL().toString() + "/" + result.getUidpk()), HttpStatus.CREATED);
} `
My endpoints
private static final String CUSTOMER_SEARCH_USER_ID_ENDPOINT = "/customers/{userId:.+}";
private static final String CREATE_CUSTOMER_ENDPOINT= "/customer";
private static final String UPDATE_CUSTOMER_ENDPOINT= "/customer";
private static final String DELETE_CUSTOMER_ENDPOINT = CREATE_CUSTOMER_ENDPOINT + "/{uidpk}";
This is the response of Postman
Postman sample
When you send JSON payloads in HTTP request, you need to specify Content-Type HTTP header with value application/json.
I want to add a header parameter field in the auto-generated swagger ui documentation of my rest service. I use Spring and Springfox.
public ResponseEntity<User> saveNewUser(
#ApiParam(value = "the user to create", required = true) #RequestBody User user) throws RestServiceException {
userService.save(user);
return new ResponseEntity<User>(user, HttpStatus.OK);
}
As you see I have already a body type parameter. I just want to add a header type one.
I prefer to use #ApiImplicitParam after my #RequestMapping rather than as function parameters because generally you might process your headers in a filter (eg authentication) and you are not needing the values in that method.
Besides if you need them in the method Swagger auto provides the field for a #HeaderParam
This style also Improves readability and flexibility when some calls need headers and other don't.
Example
#PostMapping
#ApiImplicitParam(name = "Authorization", value = "Access Token", required = true, allowEmptyValue = false, paramType = "header", dataTypeClass = String::class, example = "Bearer access_token")
fun addJob(jobRequest: Job): ResponseEntity<*>{}
If all or most for your endpoints need header that I'll rather configure it as seen here
If you have to declare several header params, you need to use the #ApiImplicitParams annotation:
#PostMapping
#ApiImplicitParams({
#ApiImplicitParam(name = "Authorization", value = "Access Token", required = true, allowEmptyValue = false, paramType = "header", dataTypeClass = String.class, example = "Bearer access_token"),
#ApiImplicitParam(name = "X-Custom-Header", value = "A Custom Header", required = true, allowEmptyValue = false, paramType = "header", dataTypeClass = String.class, example = "my header example")
})
fun addJob(jobRequest: Job): ResponseEntity<*>{}
I just added #RequestHeader(value="myHeader") String headerStr :
public ResponseEntity<User> saveNewUser(
#RequestHeader(value="myHeader") String headerStr,
#ApiParam(value = "the user to create", required = true) #RequestBody User user) throws RestServiceException {
userService.save(user);
return new ResponseEntity<User>(user, HttpStatus.OK);
}
(import org.springframework.web.bind.annotation.RequestHeader;)
You can also add a global header on every service in your documentation with the solution described here : Spring + Springfox + Header Parameters
If you are having more header parameters, then every API will have that many #RequestHeader
To avoid this and your API looks simple you can use HeaderInterceptor to capture the header information.
In preHandle() , you need to extract the headerInfo in to a an Object and set it as RequestAttribute
public class MyHeaderInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HeaderVo headerVo = HeaderVo.createReqHeaderinput(
request.getHeader("authorization"),
request.getHeader("contentType"),
request.getHeader("myHeaderParam0"),
request.getHeader("myHeaderParam1"),
request.getHeader("myHeaderParam3"),
request.getHeader("myHeaderParam4"),
request.getHeader("myHeaderParam5")
);
// You can do any validation of any headerInfo here.
validateHeader(headerVo);
request.setAttribute("headerName", headerVo);
return true;
}
}
Your API will looks like the below with a #RequestAttribute("headerName")
public #ResponseBody
ResponseEntity<MyResponse> getSomeApi(
//Headers common for all the API's
#RequestAttribute("headerName") HeaderVo header ,
#ApiParam(value = "otherAPiParam", required = true, defaultValue = "")
#PathVariable(value = "otherAPiParam") String otherAPiParam,
#ApiParam(value = "otherAPiParam1", required = true, defaultValue = "")
#RequestParam(value = "otherAPiParam1") String otherAPiParam1,
#ApiParam(value = "otherAPiParam2, required = true, defaultValue = "")
#RequestParam(value = "otherAPiParam2") String otherAPiParam2
) throws MyExcp {
....
}
Your Swagger still should describes all headers of the API, for that you can add parameters in swagger Docket, SwaggerConfig
Please note ignoredParameterTypes, we mentioned to ignore HeaderVo, because that is internal to the application. swagger doesnt require to show that
#Bean
public Docket postsApi() {
//Adding Header
ParameterBuilder aParameterBuilder = new ParameterBuilder();
List<Parameter> aParameters = new ArrayList<Parameter>();
aParameters.clear();
aParameterBuilder.name("myHeaderParam0").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
aParameters.add(aParameterBuilder.build());
aParameterBuilder.name("myHeaderParam1").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
aParameters.add(aParameterBuilder.build());
....
....
return new Docket(DocumentationType.SWAGGER_2).groupName("public-api")
.apiInfo(apiInfo()).select().paths(postPaths()).build().ignoredParameterTypes(HeaderVo.class).globalOperationParameters(aParameters);
}
sujith kasthoori's answer is the correct answer if one wants to send a header key-value from swagger UI for every end point. Its just the case that ParameterBuilder & Parameter are deprecated now.
List<RequestParameter> globalRequestParameters = new ArrayList<>();
RequestParameterBuilder customHeaders = new RequestParameterBuilder();
customHeaders.name("X-TenantID").in(ParameterType.HEADER)
.required(true)
.build();
Above is for when a header parameter named "X-TenantID" is mandatory to be sent with every request.
Then it needs to be added to Docket definition , .globalRequestParameters(globalRequestParameters)