Optional query string enum parameter - openapi, springboot - spring-boot

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.

Related

Jackson + KotlinModule: Conflicting/ambiguous property name definitions (implicit name 'isFoo')

Stumbling through a strange behaviour in Jackson when used with KotlinModule. Trying to deserialize a JSON object with isXxx-Boolean and xxx-none-Boolean property. Any solution how to deal with this?
data class FooObject(
#JsonProperty("isFoo")
val isFoo: Boolean,
#JsonProperty("foo")
val foo: String,
)
#Test
fun `deserialization should work` (){
val serialized = """
{
"isFoo": true,
"foo": "bar"
}
""".trimIndent()
val objectMapper: ObjectMapper = Jackson2ObjectMapperBuilder()
.modules(KotlinModule())
.build()
val deserialized = objectMapper.readValue(serialized, FooObject::class.java)
assertNotNull(deserialized)
}
throws
Results in
java.lang.IllegalStateException: Conflicting/ambiguous property name definitions (implicit name 'isFoo'): found multiple explicit names: [isFoo, foo], but also implicit accessor: [method org.dnltsk.Test$FooObject#getFoo()][visible=true,ignore=false,explicitName=false], [method org.dnltsk.Test$FooObject#isFoo()][visible=true,ignore=false,explicitName=false]
By removing the #JsonProperty-annotations the exception turns to
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Duplicate creator property "isFoo" (index 0 vs 1) for type `org.dnltsk.Test$FooObject`
at [Source: (String)"{
"isFoo": true,
"foo": "bar"
}"; line: 1, column: 1]
Add the following annotation to the top of your data class:
#JsonAutoDetect(
getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE,
)

What is the Swagger 2 Annotation for the responseContainer?

I working on a Migration from Springfox to Springdoc with Swagger version 2.1.9.
Therefore the Annotations must be rewritten and I cant find the equivalent Annotations for the old Swagger Annotations.
I have this API Controller:
#GetMapping
#ApiOperation(value = "Load Building")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "OK", response = Building.class, responseContainer = "Page")
})
public ResponseEntity<Page<Building>> getBuilding(Pageable building) {
final Page<Building> building = buildingrepo.findAll(page).map(bw -> mapper.map(bd, Building.class));
return ResponseEntity.ok().body(building);
With the new Swagger Annotation it must be re-written, but I don`t know how i put the "Building.class" into the Pageable in the Response Schema. I cant use "responseContainer" anymore
#GetMapping
#Operation(summary = "Load Building")
#ApiResponses(value = {
#ApiResponse(responseCode = "200",
description = "OK",
content = #Content(schema = #Schema(implementation = Building.class))) // <--- Here i need the Page class somehow as Container!!!
})
public ResponseEntity<Page<Building>> getBuilding(Pageable building) {
final Page<Building> building = buildingrepo.findAll(page).map(bw -> mapper.map(bd, Building.class));
return ResponseEntity.ok().body(building);
The Output Response in the Api Docs schould look like this:
responses:
200:
schema:
$ref: "#/definitions/Page<Building>"
And also in the Swagger UI as Example:
{
"content": [
{ Building: "" }
]
}
I cant find the right Parameter for the "responseContainer"
Swagger 2's equivalent to a responseContainer is to wrap the Schema in an ArraySchema. For an endpoint returning List<Foo> :
#ApiResponses(#ApiResponse(responseCode = "200", description = "List of Foos",
content = #Content(array = #ArraySchema(uniqueItems = false,
schema = #Schema(implementation = com.mycompany.Foo.class))
)))
Note uniqueItems is false by default so can be omitted above. However, explicitly set this value to true if you're returning a Set instead of a List.
References:
https://docs.swagger.io/swagger-core/v2.1.1/apidocs/io/swagger/v3/oas/annotations/media/ArraySchema.html

passing json in json to spring controller

I am trying to pass json object to spring controller and I manage to do that, but value of one property is in json and I think that I have problem because of it. But there is no other way to pass that data. Code is below,
data class:
#Entity
data class Section(
#Id
#GeneratedValue
val id: Long = 0L,
val name: String = "",
var text: String,
#ManyToOne
var notebook: Notebook
)
Controller code:
#PutMapping("/sections/{id}")
fun updateSection(#RequestBody section: Section, #PathVariable id: Long): Section =
sectionRepository.findById(id).map {
it.text = section.text
it.notebook = section.notebook
sectionRepository.save(it)
}.orElseThrow { SectionNotFoundException(id) }
javascript sending post to api:
function updateApi(data) {
axios.put(MAIN_URL + 'sections/' + data.id, {
data
})
.then(showChangesSaved())
.catch(ShowErrorSync());
}
function saveSection() {
var data = JSON.parse(window.sessionStorage.getItem("curr-section"));
data.text = JSON.stringify(element.editor).toString();
updateApi(data);
}
I get error like this:
2020-11-18 15:06:24.052 WARN 16172 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Instantiation of [simple type, class org.dn.model.Section] value failed for JSON property text due to missing (therefore NULL) value for creator parameter text which is a non-nullable type; nested exception is com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class org.dn.model.Section] value failed for JSON property text due to missing (therefore NULL) value for creator parameter text which is a non-nullable type
at [Source: (PushbackInputStream); line: 1, column: 375] (through reference chain: org.dn.model.Section["text"])]
so text in element.editor is JSON formatted string and I need to pass it as it is to controller. Is there any way to do that? I tried searching, but I can't find json in json help...
Whole project is available on github
What does your json looks like? If I check out your project and run the following two tests:
one with Section as an object as request body
one with Section as json
Both will succeed. So the problem might lie in your JSON:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class HttpRequestTest {
#LocalServerPort
private val port = 0
#Autowired
private val restTemplate: TestRestTemplate? = null
#Test
fun sectionAsObject() {
val section = Section(0L, "2L", "text", Notebook(1L, "1", "2"))
assertThat(restTemplate!!.put("http://localhost:$port/sections/123", section
)).isNotNull
}
#Test
fun sectionAsJson() {
val sectionAsJson = """
{
"id": 0,
"name": "aName",
"text": "aText",
"noteBook": {
"id": 0,
"name": "aName",
"desc": "2"
}
}
""".trimIndent()
assertThat(restTemplate!!.put("http://localhost:$port/sections/123", sectionAsJson
)).isNotNull
}
}
BTW: it is not a pretty good habit to expose your database ids, which is considered to be a security risk as it exposes your database layer. Instead, you might want to use a functional unique key ;)

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

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.

#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

Resources