Swagger V3 Annotation Enum - java

I am trying to get swagger annotations to generate for the code below. Below is my API endpoint, I removed the response etc to keep this very basic. It takes in a body request of UMQueryRequest.
public void query( #RequestBody(description = "Unified Model Query Request", required = true,
content = #Content(
schema = #Schema(implementation = UMQueryRequest.class))) UMQueryRequest request) {
//Implementation Here:
}
UMQueryRequest has an enum property ModelPurpose inside of it that swagger will not generate.
#Schema(description = "UMQueryRequest")
public class UMQueryRequest {
#JsonProperty
private ModelPurpose modelPurpose;
}
ModelPurpose.class
#Schema(enumAsRef = true, implementation = ModelPurpose.class)
public enum ModelPurpose {
Default {
//Implementation Here
}, Alternative {
//Implementation Here
}
}
Any Ideas on how to make swagger generate this code? I have tried a lot of stack overflow but nothing seems to work. Thanks.

Related

How to hide endpoints from OpenAPI documentation with Springdoc

Springdoc automatically generates a API documentation for all handler methods. Even if there are no OpenAPI annotations.
How can I hide endpoints from the API documentation?
The #io.swagger.v3.oas.annotations.Hidden annotation can be used at the method or class level of a controller to hide one or all endpoints.
(See: https://springdoc.org/faq.html#how-can-i-hide-an-operation-or-a-controller-from-documentation)
Example:
#Hidden // Hide all endpoints
#RestController
#RequestMapping(path = "/test")
public class TestController {
private String test = "Test";
#Operation(summary = "Get test string", description = "Returns a test string", tags = { "test" })
#ApiResponses(value = { #ApiResponse(responseCode = "200", description = "Success" ) })
#GetMapping(value = "", produces = MediaType.TEXT_PLAIN_VALUE)
public #ResponseBody String getTest() {
return test;
}
#Hidden // Hide this endpoint
#PutMapping(value = "", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseStatus(HttpStatus.OK)
public void setTest(#RequestBody String test) {
this.test = test;
}
}
Edit:
Its also possible to generate the API documentation only for controllers of specific packages.
Add following to your application.properties file:
springdoc.packagesToScan=package1, package2
(See: https://springdoc.org/faq.html#how-can-i-explicitly-set-which-packages-to-scan)
If you are working with Swagger Api and you want to hide specific endpoint then use #ApiOperation(value = "Get Building",hidden=true) on that endpoint...hidden attribute should be true.
#RestController
#Api(tags="Building")
#RequestMapping(value="/v2/buildings")
public class BuildingsController {
#ApiOperation(value = "Get Building",hidden=true)
#GetMapping(value = "/{reference}")
public Account getBuildings(#PathVariable String reference) {
....
}
Its also possible to generate the API doc only for specific Path.
Add following to your application.yml file:
springdoc:
paths-to-match: /api/**, /v1

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 use #Api swagger annotation in sub-resource class?

In my project I have some sub-resources, correctly implemented according to the Jersey framework guidelines.
However, I have a problem in generating the openapi.json file (generated by swagger-maven-plugin).
In particular, I would like to be able to use the #Api swagger annotation on classes that implement sub-resources, to apply some properties, such as authorizations.
The problem is that if I use the #Api annotation on the sub-resource classes, swagger sees those classes not only as a sub-resources, but also as resources. The result is that in the openapi.json file, for each sub-resource, a same resource is generated (which should not exist).
The root resource:
#Path("root")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#Api(value = "/root", authorizations = {
#Authorization(value = "auth", scopes = {})
})
public class ExperienceResource {
#Path("/sub_a")
public SubResourceA getSubA() {
return new SubResourceA();
}
#Path("/sub_b")
public SubResourceB getSubB() {
return new SubResourceB();
}
}
The sub-resource:
#Api(authorizations = {
#Authorization(value = "auth", scopes = {})
})
public class SubResourceA {
#GET
...
}
I also tried using #Api(value="") or #Api(value="sub_a"), but they don't work or make things worse.
Obviously, if I remove the #Api annotation everything works correctly, but I am forced to apply the properties operation by operation.
Any suggestions?
The solution was quite simple. To ensure that the class that implements the sub-resource is seen by swagger just as a sub-resource (and not as a resource too) just add the property hidden = true in the #Api annotation
#Api(hidden = true, authorizations = {
#Authorization(value = "auth", scopes = {})
})
public class SubResourceA {
#GET
...
}

Spring MVC - The request sent by the client was syntatically incorrect

I have a simple controller:
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<MyResponse> print(#RequestBody final RequestModel request) throw ApiException {
return null;
}
And in my RequestModel:
class RequestModel {
private String name;
private CustomData data;
}
CustomData:
class CustomData {
private String data;
}
When I make POST request without the "data" field, it works. But if I add the "data" field, I'm getting a 400, The request sent by the client was syntatically incorrect.
O dont know If you wrote all the code, but tou should implements serializable and write setters and getters.
But, answering your question, you should annotate your fields with #JsonProperty to specify the required flag.
Your posted JSON should be something like this :
{
"name":"Luke",
"data": {
"data":"I am your father"
}
}
OBS: if you are using Postman, please set the header : key: Content-Type, value: application/json
You should specify an endpoint:
Example :
#PostMapping("/data")
Instead of
#RequestMapping(method = { RequestMethod.POST })
If you are using default port, try again the post to :
http://localhost:8080/data
OBS: RequestModel and CustomerData must have getters and setters.

Spring MVC wrap a lot of #RequestParam to an Object [duplicate]

Suppose i have a page that lists the objects on a table and i need to put a form to filter the table. The filter is sent as an Ajax GET to an URL like that: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z
And instead of having lots of parameters on my Controller like:
#RequestMapping(value = "/action")
public #ResponseBody List<MyObject> myAction(
#RequestParam(value = "page", required = false) int page,
#RequestParam(value = "prop1", required = false) String prop1,
#RequestParam(value = "prop2", required = false) String prop2,
#RequestParam(value = "prop3", required = false) String prop3) { ... }
And supposing i have MyObject as:
public class MyObject {
private String prop1;
private String prop2;
private String prop3;
//Getters and setters
...
}
I wanna do something like:
#RequestMapping(value = "/action")
public #ResponseBody List<MyObject> myAction(
#RequestParam(value = "page", required = false) int page,
#RequestParam(value = "myObject", required = false) MyObject myObject,) { ... }
Is it possible?
How can i do that?
You can absolutely do that, just remove the #RequestParam annotation, Spring will cleanly bind your request parameters to your class instance:
public #ResponseBody List<MyObject> myAction(
#RequestParam(value = "page", required = false) int page,
MyObject myObject)
I will add some short example from me.
The DTO class:
public class SearchDTO {
private Long id[];
public Long[] getId() {
return id;
}
public void setId(Long[] id) {
this.id = id;
}
// reflection toString from apache commons
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
Request mapping inside controller class:
#RequestMapping(value="/handle", method=RequestMethod.GET)
#ResponseBody
public String handleRequest(SearchDTO search) {
LOG.info("criteria: {}", search);
return "OK";
}
Query:
http://localhost:8080/app/handle?id=353,234
Result:
[http-apr-8080-exec-7] INFO c.g.g.r.f.w.ExampleController.handleRequest:59 - criteria: SearchDTO[id={353,234}]
I hope it helps :)
UPDATE / KOTLIN
Because currently I'm working a lot of with Kotlin if someone wants to define similar DTO the class in Kotlin should have the following form:
class SearchDTO {
var id: Array<Long>? = arrayOf()
override fun toString(): String {
// to string implementation
}
}
With the data class like this one:
data class SearchDTO(var id: Array<Long> = arrayOf())
the Spring (tested in Boot) returns the following error for request mentioned in answer:
"Failed to convert value of type 'java.lang.String[]' to required type
'java.lang.Long[]'; nested exception is
java.lang.NumberFormatException: For input string: \"353,234\""
The data class will work only for the following request params form:
http://localhost:8080/handle?id=353&id=234
Be aware of this!
Since the question on how to set fields mandatory pops up under each post, I wrote a small example on how to set fields as required:
public class ExampleDTO {
#NotNull
private String mandatoryParam;
private String optionalParam;
#DateTimeFormat(iso = ISO.DATE) //accept Dates only in YYYY-MM-DD
#NotNull
private LocalDate testDate;
public String getMandatoryParam() {
return mandatoryParam;
}
public void setMandatoryParam(String mandatoryParam) {
this.mandatoryParam = mandatoryParam;
}
public String getOptionalParam() {
return optionalParam;
}
public void setOptionalParam(String optionalParam) {
this.optionalParam = optionalParam;
}
public LocalDate getTestDate() {
return testDate;
}
public void setTestDate(LocalDate testDate) {
this.testDate = testDate;
}
}
//Add this to your rest controller class
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String testComplexObject (#Valid ExampleDTO e){
System.out.println(e.getMandatoryParam() + " " + e.getTestDate());
return "Does this work?";
}
I have a very similar problem. Actually the problem is deeper as I thought. I am using jquery $.post which uses Content-Type:application/x-www-form-urlencoded; charset=UTF-8 as default. Unfortunately I based my system on that and when I needed a complex object as a #RequestParam I couldn't just make it happen.
In my case I am trying to send user preferences with something like;
$.post("/updatePreferences",
{id: 'pr', preferences: p},
function (response) {
...
On client side the actual raw data sent to the server is;
...
id=pr&preferences%5BuserId%5D=1005012365&preferences%5Baudio%5D=false&preferences%5Btooltip%5D=true&preferences%5Blanguage%5D=en
...
parsed as;
id:pr
preferences[userId]:1005012365
preferences[audio]:false
preferences[tooltip]:true
preferences[language]:en
and the server side is;
#RequestMapping(value = "/updatePreferences")
public
#ResponseBody
Object updatePreferences(#RequestParam("id") String id, #RequestParam("preferences") UserPreferences preferences) {
...
return someService.call(preferences);
...
}
I tried #ModelAttribute, added setter/getters, constructors with all possibilities to UserPreferences but no chance as it recognized the sent data as 5 parameters but in fact the mapped method has only 2 parameters. I also tried Biju's solution however what happens is that, spring creates an UserPreferences object with default constructor and doesn't fill in the data.
I solved the problem by sending JSon string of the preferences from the client side and handle it as if it is a String on the server side;
client:
$.post("/updatePreferences",
{id: 'pr', preferences: JSON.stringify(p)},
function (response) {
...
server:
#RequestMapping(value = "/updatePreferences")
public
#ResponseBody
Object updatePreferences(#RequestParam("id") String id, #RequestParam("preferences") String preferencesJSon) {
String ret = null;
ObjectMapper mapper = new ObjectMapper();
try {
UserPreferences userPreferences = mapper.readValue(preferencesJSon, UserPreferences.class);
return someService.call(userPreferences);
} catch (IOException e) {
e.printStackTrace();
}
}
to brief, I did the conversion manually inside the REST method. In my opinion the reason why spring doesn't recognize the sent data is the content-type.
While answers that refer to #ModelAttribute, #RequestParam, #PathParam and the likes are valid, there is a small gotcha I ran into. The resulting method parameter is a proxy that Spring wraps around your DTO. So, if you attempt to use it in a context that requires your own custom type, you may get some unexpected results.
The following will not work:
#GetMapping(produces = APPLICATION_JSON_VALUE)
public ResponseEntity<CustomDto> request(#ModelAttribute CustomDto dto) {
return ResponseEntity.ok(dto);
}
In my case, attempting to use it in Jackson binding resulted in a com.fasterxml.jackson.databind.exc.InvalidDefinitionException.
You will need to create a new object from the dto.
Yes, You can do it in a simple way. See below code of lines.
URL - http://localhost:8080/get/request/multiple/param/by/map?name='abc' & id='123'
#GetMapping(path = "/get/request/header/by/map")
public ResponseEntity<String> getRequestParamInMap(#RequestParam Map<String,String> map){
// Do your business here
return new ResponseEntity<String>(map.toString(),HttpStatus.OK);
}
Accepted answer works like a charm but if the object has a list of objects it won't work as expected so here is my solution after some digging.
Following this thread advice, here is how I've done.
Frontend: stringify your object than encode it in base64 for submission.
Backend: decode base64 string then convert the string json into desired object.
It isn't the best for debugging your API with postman but it is working as expected for me.
Original object: { page: 1, size: 5, filters: [{ field: "id", value: 1, comparison: "EQ" }
Encoded object: eyJwYWdlIjoxLCJzaXplIjo1LCJmaWx0ZXJzIjpbeyJmaWVsZCI6ImlkUGFyZW50IiwiY29tcGFyaXNvbiI6Ik5VTEwifV19
#GetMapping
fun list(#RequestParam search: String?): ResponseEntity<ListDTO> {
val filter: SearchFilterDTO = decodeSearchFieldDTO(search)
...
}
private fun decodeSearchFieldDTO(search: String?): SearchFilterDTO {
if (search.isNullOrEmpty()) return SearchFilterDTO()
return Gson().fromJson(String(Base64.getDecoder().decode(search)), SearchFilterDTO::class.java)
}
And here the SearchFilterDTO and FilterDTO
class SearchFilterDTO(
var currentPage: Int = 1,
var pageSize: Int = 10,
var sort: Sort? = null,
var column: String? = null,
var filters: List<FilterDTO> = ArrayList<FilterDTO>(),
var paged: Boolean = true
)
class FilterDTO(
var field: String,
var value: Any,
var comparison: Comparison
)

Categories