Annotating #FormParam fields with Swagger-UI #ApiParam - java

I have built a RestEasy API and linked it with Swagger UI. A task I have been asked to complete is to, find a way to reduce the query parameters in the method signature and handle them in some sort of "DTO".
My original implementation would be similar to:
#GET
#ApiOperation(value = "echo test value", notes = "echo test notes")
#ApiResponse(code = HttpServletResponse.SC_OK, message = "Response.status.OK")
public Response echoTest(
#ApiParam("id") #QueryParameter("id") final int id,
#ApiParam("fName") #QueryParameter("fName") final String fName,
#ApiParam("sName") #QueryParameter("sName") final String sName) {
// handle request
}
I have extracted the query-parameter handling to a DTO, although now I am unsure how to handle the Swagger-UI side of things. I have tried to annotate the fields in the DTO athough as I guessed, this did not work. My current solution without correct swagger-ui interaction:
#GET
#ApiOperation(value = "echo test value", notes = "echo test notes")
#ApiResponse(code = HttpServletResponse.SC_OK, message = "Response.status.OK")
public Response echoTest(#ApiParam("form") #FormParam QueryDTO dto) {
//Handle request
}
QueryDTO.java:
public class QueryDTO {
#ApiParam(name = "id", value = "user id") #QueryParam("id") private int id;
#ApiParam(name = "fName", value = "user first name") #QueryParam("fName") private String fName;
#ApiParam(name = "sName", value = "user surname") #QueryParam("sName) private String sName;
// Getters,setters etc
}
Does SwaggerUI support this type of feature? Is there an alternative approach I could take which would suit my use case? Any suggestions or help is appreciated, thanks.

The issue here isn't Swagger-UI but rather Swagger-Core.
Swagger-Core doesn't support RESTEasy's #Form annotation and only supports standard JAX-RS annotations.
I was unfamiliar with that annotation until you mentioned it, but it looks like it acts the same way as #BeanParam which was introduced in JAX-RS 2.0. Support for it should be provided with RESTEasy 3.0 and above. Swagger-core is able to process #BeanParam's in order to produce proper documentation.
If you still want just support for #Form, you'd have to open an issue on Swagger-Core's repository.

Related

Bad Request response with name of missing field - Spring Boot

I have an API endpoint that get a name and description parameters (both are mandatory)
createSomething(#RequestParam(value = "name") String name,#RequestParam(value = "description") String description)
If the client is not providing any of these he will get 400 Bad Request
Is there a way for me to tell the client which field is missing ? give more information for the "Bad Request" response
Update: Note that the parameters must be mandatory since I want that OpenAPI will detect that these parameters are mandatory. So solutions like making then "optional" and checking inside the body of the function is not what I am looking for
I see multiple answers but no one is specific enough.
1)
Spring by default has the capability of reporting in the error message what parameter was missing or other violations in the request.
However since spring boot version 2.3 the specific error messages are hidden, so that no sensitive information can be disclosed to the user.
You can use the property server.error.include-message: always which was the default mechanism before 2.3 version and allow spring to write error messages for you again.
2)
If you can't afford this because other sensitive info could be leaked from other exceptions, then you have to provide your own exception handler for this specific case
The following can be placed either in the same controller, or in another class marked with #ControllerAdvice
#ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity handleMissingParams(MissingServletRequestParameterException ex) {
return ResponseEntity.badRequest().body(String.format("Missing parameter with name:%s", ex.getParameterName()));
}
As #Shubam said, you can use the defaultValue attribute of #RequestParam annotation by setting the required attribute to true since both the parameters are mandatory.
Here is an example of how you could do it,
private final String DEFAULT_NAME = "Default Name";
private final String DEFAULT_DESC = "Default Desc";
#RequestMapping(value = "/get", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public ResponseEntity<String> createSomething(#RequestParam(required = true, name = "name", defaultValue = "Default Name") String name,
#RequestParam(required = true, name = "description", defaultValue = "Default Desc") String desc){
if(DEFAULT_NAME.equals(name)){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Field Name is missing");
}
if(DEFAULT_DESC.equals(desc)){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Field Desc is missing");
}
return ResponseEntity.ok(String.format("Hello, %s!",name));
}
You can use validation with a customised message :
#GetMapping("/name-for-month")
public String getNameOfMonthByNumber(#RequestParam #Min(1) #Max(value = 12, message = “month number has to be less than or equal to 12”) Integer month) {
// ...
}
There are many ways of handling errors for Rest find below a link of at least 5 solutions for your issue :
ExceptionHandler
HandlerExceptionResolver (ResponseStatusExceptionResolver this is the most adducate for your case or the 4th one if you use spring 5+)
ControllerAdvice
ResponseStatusException
Handle the Access Denied in Spring Security
https://www.baeldung.com/exception-handling-for-rest-with-spring
Since both parameters are mandatory you'll be getting 400 (bad request) if you try to send the request without paramters.
A workaround could be making request parameters non-mandatory(so that request can be sent without parameters) and provide a default value in case no parameter is provided
createSomething(#RequestParam(value = "name", required=false, defaultValue = null) String name,#RequestParam(value = "description", required=false, defaultValue = null) String description)
In the function, you can check for null like the following -
if (name == null) // name parameter is not provided
if (description == null) // description paramter is not provided
And, based on conditions you can also send error reponse if any one/more paramter not provided in the request.

JAXB - #XMLTransient fields disappear when returned to UI

My application uses several POJOs in the backend to marshall data from the backend to the UI. The data comes from the DB as a string, it gets mapped using Jackson into our POJOs, and then we return the object in the API call using #Produces(MediaType.APPLICATION_JSON). When migrating the app to JBoss 7 EAP, we noticed that any field marked with #XmlTransient was not getting marshalled into JSON when it was returned to the UI. The POJO object had all the fields populated, but on the UI end they would not show up in the JSON string at all. Example:
//class POJO
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class FetchDataVO {
#XmlTransient
private String Id;
private String name;
#XmlTransient
private String domain;
}
And our API response would look like:
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(value = "getUserById", nickname = "getUserById")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Success", response = FetchDataVO.class),
#ApiResponse(code = 401, message = "Unauthorized"),
#ApiResponse(code = 403, message = "Forbidden"),
#ApiResponse(code = 404, message = "Not Found"),
#ApiResponse(code = 500, message = "Failure")})
public #ResponseBody
#Valid fetchDataVO getUserById(
#PathParam("id") String id){
FetchDataVO fetchVO = callDataBase.getUserById(id);
//All the data will be present here, everything is correct so far
log.info("fetchVO contents - " + fetchVO.printDetails());
return fetchVO;
}
Our backend code would print out the POJO with all the fields correct. However, when we call it in our UI, we see the response as:
{"name":null}
The other fields don't even show up. Like I mentioned, this only happened after migrating to version 3.0+ of jackson due to the JBoss upgrade.
Jackson is capable of recognizing JAXB annotations to configure the serialization/deserialization.
Unfortunately, at some point Wildfly/JBoss JAX-RS implementation, RestEasy, enabled this feature by default. So, if your bean is annotated with #XmlRootElement, Jackson will honour #XmlTransient annotations and hence ignore the field.
As a workaround to disable it, you can use a JAX-RS ContextResolver to configure the Jackson ObjectMapper without this feature.
To get a plain ObjectMapper just add something like this on your REST module:
#Provider
public class JacksonObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public JacksonObjectMapperContextResolver() {
mapper = new ObjectMapper();
// additional configuration here if needed
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}

How to handle invalid Number in Javax validation for Spring Boot Rest and Jackson Deserializer

I have a Java Pojo class which is expected as a #RequestBody in my Rest Controller. This Java Pojo class has an Integer field.
When user is calling the Rest API and they pass Integer field value as a Junk String, then Jackson is throwing InvalidFormatException. I instead want to use Javax Validator framework annotation to handle this error and show a meaningful error message in response. Unfortunately Jackson deserialization from JSON to Java happens before Javax validation, therefore my #Digits validation never gets invoked.
Note, #NotNull gets invoked but #Digits is not getting invoked because jackson fails the request much before the call reaches Javax validation layer IMO.
Java Pojo class:
public class Board implements Serializable{
private static final long serialVersionUID = 1L;
#Digits(message = "quantity must be a number", integer = 8, fraction = 0)
private Integer quantity;
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity= quantity;
}
}
Controller class:
#Controller
#RequestMapping("boards")
public class EnrichController {
#Autowired
private BoardService boardService;
#RequestMapping(value = "", method = RequestMethod.PUT, produces = "application/json;charset=UTF-8")
public #ResponseStatus(value = HttpStatus.NO_CONTENT) #ResponseBody void updateBoard(
#RequestBody #Valid Board board) throws IllegalArgumentException, MalformedURLException {
boardService.updateUserBoard(board);
}
}
User input:
{
"quantity": "abcdef"
}
As you can see I am using Javax validation annotations #Digits & #Valid but no use because Jackson Deserialization fails while parsing the Integer field quantity.
Is there anyway you can help me to solve this situation by handling this use-case using Javax validation annotations? I reckon changing field type to String in POJO class is an expensive effort as we have to do String to Integer conversion everytime I need some business logic on that field, therefore that is not an option for me.
Well in your case the problem is that you're not conforming to the REST API exposed and try to send a string instead of a number for the quantity field. This should never happen either by you or a third party service that uses your API.
Is there anyway you can help me to solve this situation by handling this use-case using Javax validation annotations?
In any case, if you still want to fix the above, a solution will be to change "quantity" in a string type field and and added pattern matching for it
#Pattern(message = "quantity must be a number", regexp="^[0-9]*$")
private String quantity;

swagger-codegen API converts types to string

Here is my interface for my swagger app:
#SwaggerDefinition(
...
)
#Api(
produces = "application/json",
protocols = "http",
tags = {"Service"}
)
#Path("/v1/{userId}/Services")
public interface ServiceApiV1 {
#GET
#Produces(MediaType.APPLICATION_JSON)
#AsyncTimed
#ApiOperation(
value = "Retrieves a service instance",
tags = "Service"
)
#Path("/{serviceId}")
void fetchService(#Suspended AsyncResponse asyncResponse,
#ApiParam #PathParam("userId") UserId userId,
#ApiParam #PathParam("serviceId") ServiceId serviceId
);
#GET
#Produces(MediaType.APPLICATION_JSON)
#AsyncTimed
#ApiOperation(
value = "Retrieves a list of services",
tags = "Service"
)
#Path("/")
void fetchServices(#Suspended AsyncResponse asyncResponse,
#ApiParam #PathParam("userId") UserId userId
);
}
I tried to use #ApiParam(type = "UserId") but it still uses string (I also tried to use the full path to UserId, like com.myservice.UserId)
As you can see, I have a specific type for UserId and one for ServiceId. When I run swagger-codegen the API generated, however, convert these arguments to type string
Is it possible to have Swagger generate the API clients, but keep the type of PathParams as I have defined them here?
(And yes, the swagger.json has these parameters of type string which makes sense why the codegen is then generating these as a string. So I guess the correct question is how to make Swagger generate the parameters are the more advance types as they are)
UPDATE So using full path does generate the right type for my swagger.json. However, the generated API still uses a String :(

Proper way to allow json with and without root node using spring mvc and jackson

I have the following controller method:
#RequestMapping(value = "/v1/users/{userId}/settings", method = RequestMethod.POST, produces = { "application/json" }, consumes = { "application/json" })
#ResponseStatus(value = HttpStatus.CREATED)
#ResponseBody
public UserSetting createUserSetting(#PathVariable("userId") String userId,
#RequestBody Setting setting){
return userSettingService.createSetting(userId, userSetting);
}
When invoking the url /v1/users/12345/settings POST
with the payload
{"Setting":{"name":"CoolSetting", "id":"SETTING-ID"}}
the method behaves as expected.
What I would also like to accept is:
{"name":"CoolSetting", "id": "SETTING-ID"}
how do I accept the second payload version without the root tag.
The object in question looks like this
public class Setting{
#JsonProperty
private String id;
#JsonProperty
private String name;
//getters and setters removed
}
how can I tell spring to marshel using either json method?
You can use DeserializationConfig.Feature.UNWRAP_ROOT_VALUE property to achieve this result. For serialization use SerializationConfig.Feature.WRAP_ROOT_VALUE.
Detailed explanation and usage available in this blog: Jackson with and without root node

Categories