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.
Related
I create Spring RestFul API, I used swagger to test my APIs.
My case is described as below:
URL 1 (not work)
#GetMapping(value = "/api/my/{id}")
public ResponseEntity<MyDTO> getInfo(#PathVariable("id") Long courseId,
#RequestParam(defaultValue = "0", value = "pageNo") Integer pageNo,
#RequestParam(defaultValue = "25", value = "pageSize") Integer pageSize,
#RequestParam(defaultValue = "id", value = "sortBy") String sortBy) {
}
Swagger URL will generated as:
http://localhost:8080/api/my/1{?pageNo%2CpageSize%2CsortBy%7D=&pageNo=0&pageSize=25&sortBy=id
Error:
"detail": "Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long'; nested exception is java.lang.NumberFormatException: For input string:
Because: the value of id is 1{
I want to build the URL as:
http://localhost:8080/api/my/1?pageNo=0&pageSize=25&sortBy=id
URL 2 (worked- it means other components are corrected)
It worked if I change all request to PathVariable like
#GetMapping(value = "/api/my/{id}")
public ResponseEntity<MyDTO> getInfo(#PathVariable("id") Long courseId,
#PathVariable("pageNo") int pageNo, #PathVariable("pageSize") int pageSize) {
}
Swagger URL will generated as:
http://localhost:8080/api/my/1/0/1
Anyone what's wrong in this case with URL1 above ?
Thanks a lot!!!
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")
]
))
])
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
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(")"));
}
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.