How to annotate array of objects response in Swagger - java

I have to debug a REST API Java project that has been developed using Swagger.I'm new to it, so I'm a little bit confused on how to do certain things. For example, here's one method:
#GET
#Path("/location/name")
#Produces({MediaType.APPLICATION_JSON})
#Operation(
summary = "Get location information",
tags = {"Information"},
responses = {
#ApiResponse(responseCode = "200", content = #Content(schema = #Schema(implementation = LocationResponse.class)), description = "Get location information"),
#ApiResponse(responseCode = "500", description = "Error: Internal Server Error")
}
)
public Response searchLocationByName(
#Parameter(description = "Location name", required = true) #DefaultValue("Barcelona") #QueryParam("name") String locationName
) { /* METHOD CODE */ }
The #ApiResponse for the code 200 is not of type LocationResponse but of type ArrayList<LocationResponse>, as it can return more than one location. What would be the correct syntax for this change? I've been reading the documentation at https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations#operation-annotations but I couldn't find an appropriate example...
Thanks!

Use #ArraySchema instead of plain #Schema to define input or output data for array types.

For the PageDto<T> we can simply create ResponseDto which extends PageDto<T> and use it in swagger as : #ApiResponse(responseCode = "200", content = #Content(array = #ArraySchema(schema = #Schema(implementation = ResponseDto.class))), description = "Get location information"),. Thank You

Related

openapi-generator spring boot: combining json and text response

I am using the openapi-generator to create a spring-boot server. I am currently still in the experimentation phase.
The goal is that the server is able to provide multiple response media-types, depending on the media type that is accepted by the requester. According to different sources, this should be possible. See here for example of how the yaml file would then look. Other similar examples can be found here on stack overflow.
Concrete example. Let's say we have a post request where if a name is posted the name is returned (just a silly example). In case the requester sends the name John Doe and does not accept application/json, the response, in plain text, should look like this:
John Doe
In case the requester accepts application/json the response should look like this:
{"name": "John Doe"}
For explaining my question/problem I created an example spring boot server. At one point it has the path /user for which the response is:
responses:
'200':
description: The username.
content:
application/json:
schema:
properties:
name:
type: string
example: John Doe
text/plain:
schema:
type: string
example: John Doe
On the other hand I created the path /getuser (name is not really fortunate but it is for this example) which returns the following response:
'200':
description: The username.
content:
text/plain:
schema:
type: string
example: John Doe
application/json:
schema:
properties:
name:
type: string
example: John Doe
My problem is the following: for the first example, where I put the application/json first in the yaml file, the API looks like this:
default ResponseEntity<UserPost200Response> userPost(
#Parameter(name = "name", description = "The name of the user.") #Valid #RequestParam(value = "name", required = false) String name,
#Parameter(name = "UserPostRequest", description = "") #Valid #RequestBody(required = false) UserPostRequest userPostRequest
) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"name\" : \"John Doe\" }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
If, however, I would like to return a ResponseEntity<String> this gives an error since UserPost200Response is not used.
For the path /getuser, where the String response is first defined in the yaml file, my API looks like this:
default ResponseEntity<String> getuserPost(
#Parameter(name = "name", description = "The name of the user.") #Valid #RequestParam(value = "name", required = false) String name,
#Parameter(name = "UserPostRequest", description = "") #Valid #RequestBody(required = false) UserPostRequest userPostRequest
) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "\"John Doe\"";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
This API makes it possible to return a ResponseEntity<String> but not a ResponseEntity<UserPost200Response> which defines the above mentioned json-model.
One workaround that I found, would be to use the path where the string-response is declared first in the yaml file (see /getuser) in the example above and that returns a ResponseEntity<String> and override and do something like this:
default ResponseEntity<String> getuserPost(
#Parameter(name = "name", description = "The name of the user.") #Valid #RequestParam(value = "name", required = false) String name,
#Parameter(name = "UserPostRequest", description = "") #Valid #RequestBody(required = false) UserPostRequest userPostRequest
) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"name\" : \"John Doe\" }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
if (mediaType.isCompatibleWith(MediaType.valueOf("text/plain"))) {
String exampleString = "John Doe";
ApiUtil.setExampleResponse(request, "text/plain", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
In this case, I don't use the Model created by the openapi-generator and treat the JSON basically as a string.
Another option is to go for a wildcard like ResponseEntity<?> but from what I understand, except proven the contrary, this seems bad practice. I haven't figured out how to declare this in the .yml file that is used by the openapi-generator.
Neither options seem to respect the contract.
I wonder if (1) I am doing something wrong here and (2) how it could be better implemented. The goal is of course to not rewrite the API's and only implement the logic in the Controllers. Any ResponseEntity in the API's should thus not be changed.

OpenApi how to add example from resources file for #RequestBody -> #Content -> #Schema -> example

I am developing a service-based application for which I am adding openapi based annotations such as #RequestBody, #Parameter, #Schema within the #Schema I have an example field for which I can provide the example template in String format.
I have provided the example JSON string but the JSON content is huge so I would like to add it from the file present in my resources folder. But I am currently unable to load it. Can someone please let me know how can I add the example content from the file rather than String?
I tried looking and found that there is a field externalValue but I am unable to understand how to make it work. Following is the link to the documentation.
Following is the code I have which is working perfectly fine:
#Path("/generate")
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#RequestBody(description = "InputTemplate body",
content = #Content(schema = #Schema(implementation = InputTemplate.class, example = "{\n" +
" \"names\":[\n" +
" \"Batman\",\n" +
" \"Superman\",\n" +
" \"Ironman\"\n" +
" ],\n" +
" \"jobs\":[\n" +
" \"Fighting\",\n" +
" \"Fyling\",\n" +
" \"Teching\"\n" +
" ]\n" +
"}")))
public Multi<String> generate(final Map<String, Object> input) throws CustomException {
}
I would like to replace the JSON contents present example with the contents from the external file which is present in my resources folder.
After trying many things I got to know that I need to use #ExampleObject but if I add the respective annotations and try to open my Swagger UI then I am not getting the contents of the file that I have added. Rather it provides me with the data from InputTemplate.class.
Following is the modified code:
#RequestBody(description = "InputTemplate body",
content = #Content(schema = #Schema(implementation = InputTemplate.class), examples = {
#ExampleObject(name = "Example-1",
description = "Example-1 for InputTemplate.",
ref = "#/resources/Example1.json"), externalValue = "#/resources/Example2.json"
#ExampleObject(name = "Example-2",
description = "Example-2 for InputTemplate.",
ref = "#/resources/Example1.json") //externalValue = "#/resources/Example1.json"
}))
I tried to look into a similar question but the provided response does not work for me:
How to refrence files in SpringDoc OpenAPI3?
https://github.com/springdoc/springdoc-openapi/issues/1432
https://github.com/springdoc/springdoc-openapi/issues/17
As best as I can tell, the ref value seems to be expecting a url where a schema could be found? I saw someone recommend making an endpoint to return examples? This seems a bit much to me...
I decided the easiest thing to do was just add something to pull examples from the files and insert them into the OpenApi object.
I implemented an OpenApiCustomiser in my spring config, this allows me to point to files in the apps resources folder for Response examples.
I annotate the Controller method like this:
#ApiResponses(value = {
#ApiResponse(responseCode = "200",
content = { #Content(mediaType = "application/json",
schema = #Schema(implementation = SomeResponse.class,
name = "YourResponse"),
examples = {#ExampleObject(value = "#your_data_200_response.json")}) })
})
To get the above to work you then add the following OpenApiCustomiser config bean:
#Bean
public OpenApiCustomiser applyStandardOpenAPIModifications() {
return openApi -> {
Paths paths = new Paths();
openApi.getPaths().entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(stringPathItemEntry -> {
paths.addPathItem(stringPathItemEntry.getKey(), addExamples(stringPathItemEntry.getValue()));
});
openApi.setPaths(paths);
};
}
private PathItem addExamples(PathItem pathItem) {
if(pathItem.getPost() !=null) {
//Note you can also Do this to APIResponses to insert info from a file into examples in say, a 200 response.
pathItem.getPost().getRequestBody().getContent().values().stream()
.forEach(c ->{
String fileName = c.getExample().toString().replaceFirst("#","");
ObjectNode node = null;
try {
//load file from where you want. also don't insert is as a string, it wont format properly
node = (ObjectNode) new ObjectMapper().readTree(methodToReadInFileToString(fileName));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
c.setExample(node);
}
);
}
return pathItem;
}
I just load the files from a folder places in /resources which contains the .json files.

Unexpected token error using Swagger annotations

I am testing Swagger annotations using a code I copied from the Swagger-core Github.
The code excerpt I am testing comes from the static class SimpleOperations from here (line 446)
In my code, it looks like this:
(...)
#Controller("/")
class IntegratorWebController {
def convoyWebService
#Operation(
operationId = "subscribe",
description = "subscribes a client to updates relevant to the requestor's account, as identified by the input token. The supplied url will be used as the delivery address for response payloads",
parameters = {
#Parameter(in = ParameterIn.PATH, name = "subscriptionId", required = true,
schema = #Schema(implementation = Convoy.class),
style = ParameterStyle.SIMPLE, example = "example",
examples = {
#ExampleObject(name = "subscriptionId_1", value = "12345",
summary = "Subscription number 12345", externalValue = "Subscription external value 1"),
#ExampleObject(name = "subscriptionId_2", value = "54321",
summary = "Subscription number 54321", externalValue = "Subscription external value 2")
})
},
responses = {
#ApiResponse(
description = "test description",
content = #Content(
mediaType = "*/*",
schema = #Schema(
type = "string",
format = "uuid",
description = "the generated UUID",
accessMode = Schema.AccessMode.READ_ONLY,
example = "Schema example"
),
examples = {
#ExampleObject(name = "Default Response", value = "SubscriptionResponse",
summary = "Subscription Response Example", externalValue = "Subscription Response value 1")
}
))
})
def saveOrUpdateActivity(){
def result = [error:[]]
def status = OK
(...)
The only difference is that I replaced ExamplesTest.SubscriptionResponse.class to a class that exists on my code.
I am using
io.swagger.core.v3, swagger-annotations version 2.1.2
Java 11
Grails 4.0.2
I am getting:
IntegratorWebController.groovy: 28: unexpected token: # # line 28, column 2.
#Operation(
^
At the IDE it looks like this:
Javadoc says that #Parameter can be used independently in Operation or at method level to add a parameter to the operation, even if not bound to any method parameter. So the example is sound.
What is wrong?
Tks!
Because I am coding in Groovy, I have to use [] instead of {} for the array of #Parameters and #ExampleObject.
The correct code looks like this:
#Post(uri="/saveOrUpdateActivity", produces = MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#Operation(
operationId = "subscribe",
description = "subscribes a client to updates relevant to the requestor's account, as identified by the input token. The supplied url will be used as the delivery address for response payloads",
parameters = [
#Parameter(in = ParameterIn.PATH, name = "subscriptionId", required = true,
schema = #Schema(implementation = Convoy.class),
style = ParameterStyle.SIMPLE, example = "example",
examples = [
#ExampleObject(name = "subscriptionId_1", value = "12345",
summary = "Subscription number 12345", externalValue = "Subscription external value 1"),
#ExampleObject(name = "subscriptionId_2", value = "54321",
summary = "Subscription number 54321", externalValue = "Subscription external value 2")
])
],
responses = [
#ApiResponse(
description = "test description",
content = #Content(
mediaType = "*/*",
schema = #Schema(
type = "string",
format = "uuid",
description = "the generated UUID",
accessMode = Schema.AccessMode.READ_ONLY,
example = "Schema example"
),
examples = [
#ExampleObject(name = "Default Response", value = "SubscriptionResponse",
summary = "Subscription Response Example", externalValue = "Subscription Response value 1")
]
))
])

How to get Java interface #Path value from other class

I have the following kind of interface. Is there any way to get #Path("/bucket-definitions") value "/bucket-definitions" from other class?
#Path("/bucket-definitions")
#Api(
value = "Bucket definition",
authorizations = {#Authorization("token")}
)
public interface BucketDefinitionResource {
#GET
#Path("/{operator-id}")
#Produces({"application/json"})
#ApiOperation(
value = "Get all bucket definitions.",
notes = "Returns all bucket definitions.",
response = BucketDefinitionList.class
)
BucketDefinitionList get(#ApiParam(value = "Bucket definitions of the operator to be fetched.",required = true) #PathParam("operator-id") String var1, #ApiParam(value = "Page number",required = false) #DefaultValue("1") #QueryParam("page") Integer var2, #ApiParam("Items per page") #DefaultValue("20") #QueryParam("per_page") Integer var3);
}
I discovered the following solution after trying in several ways. I was only interested to get the value of #Path("/bucket-definitions") that is "bucket-definitions". It is not from any website. So it is completely my way of getting the value of #Path annotation. Other experts can suggest me a better way. Hopefully, this solution will be helpful for others.
Annotation annotation = BucketDefinitionResource.class.getAnnotations()[0];
if (annotation.toString().contains("Path")) {
String SERVICE_NAME = annotation.toString().substring(annotation.toString().indexOf("/"), annotation.toString().indexOf(")"));
}

Spring RequestBody convert JSON to String

I have a RestController class which has a method to search for a Film by its title:
#RequestMapping(value = "/film", method = RequestMethod.POST,
consumes = "application/json", produces = "application/json")
public Film getFilm(#RequestBody String filmSearch){
FilmInfo filmInfo = new FilmInfo();
Film film = filmInfo.getFilm(filmSearch);
return film;
}
If I send a json String
{
"filmSearch":"<title>"
}
to the endpoint from Postman I receive a blank response back.
I then did a
System.out.println(filmSearch)
right after entering the method to find the String filmSearch was exactly the JSON string I sent from Postman. My application is not seeing the JSON and extracting the value from filmSearch in my request to attach to the in-app String filmSearch.
If I remove the
consumes = "application/json"
part in the RequestMapping and send over a plain text string of the title it works and I get a Film object sent back as JSON.
I'd rather not use plain text in my search term though, how can I correctly have my JSON be converted into a String on entering the method?
If you add request body is String not Object. Server received is String json not OBJECT. You can try code:
#RequestMapping(value = "/film", method = RequestMethod.GET, produces = "application/json")
public Film getFilm(#RequestParam("search") String search){
FilmInfo filmInfo = new FilmInfo();
Film film = filmInfo.getFilm(search);
return film;
}
If you user postman:
URL: /flim?search=minion
Method: GET
Header: Content-Type: application/json
It's because you're passing the entire JSON payload as a string inside the 'getfilm()' function.
What you're expecting to call is getfilm(<title>), but you're actually calling is getfilm({"filmSearch":"<title>"} ), which is wrong.
The best choice would be, convert that string to JSON, say like this
JSONObject jsonstring = new JSONObject(filmSearch);
FilmInfo filmInfo = new FilmInfo();
Film film = filmInfo.getFilm(jsonstring.get("title"));
you can also ignore the 'consumes = "application/json"' from the request mapping.

Categories

Resources