Is there a better way to construct a controller with multiple parameters the might not be required? - spring-boot

I'm making a simple spring boot application and i have to get a list of objects, which i filter by parameters that aren't required. So basically the user can send the parameters, but doesn't have to. Best solution that i found is the use of #RequestParam annotation but it doesn't seem to work. If i put both parameters it works perfectly but if I set just one or none i get an error. Do i have to overload methods for every case or is there a smoother way to deal with this(if possible by still using the get request)?
My controller:
#RequestMapping(
value = "/fruits",
params = {"apple", "orange"},
method = GET)
public ResponseEntity<List<Fruit>> getFruits(
#RequestParam(value = "apple", required = false, defaultValue = "")
List<Apple> apple,
#RequestParam(value ="orange", required = false, defaultValue = "")
List<Orange> orange) {
List<Fruit> fruit = projectService.getFruits(apple, orange);
return ResponseEntity.ok().body(fruit);
}
Error:
{
"timestamp": "2019-01-20T21:26:52.287+0000",
"status": 400,
"error": "Bad Request",
"message": "Parameter conditions \"apple, orange\" not met for actual
request parameters: ",
"path": "/api/fruits"
}

Remove this line:
params ={"apple","orange"}
and it will work.
Because you don't need to call twice, it's enough with those #RequestParam annotations.

Related

Swagger SpringDoc: Expanding endpoint freezes browser tab

I have a single-endpoint Spring service that takes a large/complex XML input, and
returns a similarly formatted output. It is defined like so:
#Operation(summary = "Sample Request", operationId = " sampleReq", description = " Data Structure Response", responses = {
#ApiResponse(responseCode = "200", description = "Data Structure response with calculations ") })
#PostMapping(consumes = { MediaType.APPLICATION_XML_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE })
public ResponseEntity<ComplexStructure> calculate(#RequestBody ComplexStructure request) {
// ...
}
When I have it set up like this, loading the webpage causes it to hang after expanding the POST operation.
What I'd like to do is simply remove the (what I assume is breaking it) schema generation. For example, I'm fine if it looks like this: To achieve this example, I replaced ComplexStructure with String, but I'd rather not do that.

Does having a custom error response mean that you have to catch any exception in order to be consistent?

In my spring boot project i have a User class and its' fields have annotation constrains like #Size, #Pattern #NotNull etc.
For example
#Id
#Column(name = "name", nullable = false, length = 16, unique = true)
#NotNull
#Size(max = 16, message = "Username should be less or equal than 16 characters")
#Pattern(regexp = "[^\s]*", message = "Username should not contain whitespaces")
#Pattern(regexp = "^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$", message = "Username should should start with a letter")
private String userName;
A post request with invalid userName returns the following error response
{
"timestamp":"2021-06-28T18:02:02.720+00:00",
"status":400,
"error":"Bad Request",
"message":"Validation failed for object='user'. Error count: 1",
"errors":[
{
"codes":[
"Pattern.user.userName",
"Pattern.userName",
"Pattern.java.lang.String",
"Pattern"
],
"arguments":[
{
"codes":[
"user.userName",
"userName"
],
"arguments":null,
"defaultMessage":"userName",
"code":"userName"
},
[
],
{
"defaultMessage":"^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$",
"arguments":null,
"codes":[
"^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$"
]
}
],
"defaultMessage":"Username should should start with a letter",
"objectName":"user",
"field":"userName",
"rejectedValue":"5",
"bindingFailure":false,
"code":"Pattern"
}
],
"path":"/signup"
}
Before questioning if this kind of error format is what i need, i didn't like it so i tried to make my own like in this guide Baeldung
I have a global controller now to deal with custom errors like when the username is taken.
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {...}
This is what i get now
{
"timestamp": "29-06-2021 05:05:03",
"status": "BAD_REQUEST",
"message": "Invalid input",
"path": "/signup",
"errors": [
{
"field": "userName",
"message": "Username should should start with a letter",
"rejectedValue": "5"
}
]
}
I suppose a good API means that you have to be consistent, that is we always have to return an error response with the same structure.
I have override some ResponseEntityExceptionHandler's method in order to catch other errors but there are still many methods to override. Here is a list of the remaining methods.
// handleBindException
// handleTypeMismatch
// handleMissingServletRequestPart
// handleMissingServletRequestParameter
// handleMethodArgumentTypeMismatch
// handleConstraintViolation
// handleHttpMediaTypeNotAcceptable
// handleMissingPathVariable
// handleServletRequestBindingException
// handleConversionNotSupported
// handleHttpMessageNotWritable
// handleAsyncRequestTimeoutException
My questions:
Do i have to catch all these exceptions? To be more specific, is it always possible to take all these exceptions no matter how your domains, controllers, services work?
Can you please write for each of these exceptions a bad request that will cause them to be thrown? Please, don't just tell me when they will be thrown. I'm new to spring and i won't be able to understand without an example.
How about overriding the below method from ResponseEntityExceptionHandler, as it is being invoked by all methods mentioned in the query.
protected ResponseEntity<Object> handleExceptionInternal()
And have your own logic to check the instance of exception and provide different kind of error response to client.

Optional query string enum parameter - openapi, springboot

I have an OpenApi spec:
paths:
/lessons:
get:
tags:
- lesson
operationId: getLessons
parameters:
- in: query
name: daysOfWeek
schema:
type: array
items:
$ref: '#/components/schemas/DaysOfWeekEnum'
Using swagger codegen this generates an endpoint like:
#ApiOperation(value = "Get a collection lessons", nickname = "getLessons", notes = "", response = LessonDto.class, responseContainer = "List", tags={ "lesson", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "List of Lessons", response = LessonDto.class, responseContainer = "List") })
#RequestMapping(value = "/lessons",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<List<LessonDto>> _getLessons(#ApiParam(removed for brevity) #Valid #RequestParam(value = "daysOfWeek", required = false, defaultValue="new ArrayList<>()") List<DaysOfWeekEnum> daysOfWeek) {
return getLessons(daysOfWeek);
}
I use TestRestTemplate in a test like so:
ResponseEntity<List<LessonDto>> lessonDtos =
testRestTemplate.exchange("/lessons", HttpMethod.GET, null,
new ParameterizedTypeReference<List<LessonDto>>() {
});
This url works:
/lessons?daysOfWeek=THURSDAY
These urls do not:
/lessons
/lessons?daysOfWeek=SOME_INVALID_VALUE
...and I get the following error:
nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.util.ArrayList out of START_OBJECT
token
Any help appreciated.
In order to give you an empty list, you need to set the default value to be as an empty string:
#RequestParam(value = "daysOfWeek",
required = false,
defaultValue = "") List<DaysOfWeekEnum> daysOfWeek)
So the issue was related to an openapi-generator bug. Summary being:
When parameters of type array are added to an operation, the generated
Spring code includes an invalid defaultValue in the Spring MVC
parameter annotations
The fix was to upgrade to a later version of openapi-generator - 4.0.0 did the trick for me.
As an aside, the error message:
exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.util.ArrayList out of START_OBJECT token
..was a bit of a red herring and it was actually TestRestTemplate related i.e. the ParameterizedTypeReference part. Changing this to String.class identified the true nature of the error.

#ApiResponses and #ApiResponses in swagger

I have annotated my method like,
#ApiOperation( value = "Get time spent on category", response = CategoryBean.class, responseContainer = "List", notes = "API to get the time spent on all tasks based on category" )
#ApiImplicitParams( {
#ApiImplicitParam( name = "x-auth-token", value = "", dataType = "string", required = true, paramType = "header" ) } )
#ApiResponses( value = {
#ApiResponse( code = 200, message = "Success", response = CategoryBean.class, responseContainer = "List" ) } )
#RequestMapping( value = "/getTimeSpentOnCategory", method = RequestMethod.POST )
public ResponseEntity<?> getTimeSpentOnCategory( #RequestBody DashboardTaskRequestBean bean )
{/**some operation**/}
But in my swagger UI, I'am not able to get the Status code 200 and its message. Please explain why?
The following picture is the snapshot of the UI,
This is a known issue and looks like it is fixed with version 3.0.
As I see it, you are able to see the response structure at the top, but it is not visible in the table at the bottom of the screenshot.
This is also raised here and is fixed with version 3.0 :
https://github.com/swagger-api/swagger-ui/issues/1505
https://github.com/swagger-api/swagger-ui/issues/1297

Integrating swagger-maven-plugin into Spring application

I'm getting hard time trying to incorporate Swagger into my Spring application. I'm trying to just generate .json file using com.github.kongchen:swagger-maven-plugin:3.1.0 and io.swagger:swagger-core:1.5.0 for annotations, but generated file is totally empty:
{
"swagger" : "2.0",
"info" : {
"version" : "v1",
"title" : "KVS"
}
}
controller example
#RestController
#Api(
tags = { "k/v" },
value = "Main microservice entrypoint",
produces = "application/json",
consumes = "application/json",
protocols = "http,https"
)
class AbcController {
#ApiOperation(value = "/")
#ApiResponses({
#ApiResponse(code = 200, message = "Request entry", response = KvsEntry.class),
#ApiResponse(code = 404, message = "Entry not found", response = Void.class)
})
#RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<KvsEntry> create(#Validated #RequestBody KvsEntry kvsEntry) {
kvsEntry = keyValueService.saveEntry(kvsEntry);
return new ResponseEntity<>(kvsEntry, HttpStatus.OK);
}
}
I still can get some results using <springmvc>false</springmvc> configuration and JAX-RS annotations (not quite correct, i'd say), but that would be quite counterproductive. What may i be doing wrong?
Please check simple sample of working plugin with Spring MVC annotations on this repo:
https://github.com/khipis/swagger-maven-example
Plugin is sensitive on dependencies versions and presence or not of specific annotations.

Resources