I am using swagger-annotation for my spring-boot project.
I would like to return a common response code contract for each resource of my controller.
In the doc : https://github.com/swagger-api/swagger-core/wiki/annotations#apiresponses-apiresponse
they talk about #ApiResponses but I cannot put the annotation at the class level.
Here is what I did:
#Api(value = "Title",
description = "What this controller is about"
)
#ApiResponses(value = {
#ApiResponse(code = 400, message = "Bad stuff from the client"),
#ApiResponse(code = 404, message = "Item not found") }
)
public class FooBarController {
...
}
But the problem is that 400 - Bad stuff from the client and 404 - Item not found are never shown in the generated doc.
In the official doc of swagger I have seen this section: https://swagger.io/docs/specification/describing-responses/#reuse
Question: How can I create a kind of "reusable component" with java annotations ?
Thanks
According to the documentation, you can do this at the Docket level.
.useDefaultResponseMessages(false)
.globalResponseMessage(RequestMethod.GET,
newArrayList(new ResponseMessageBuilder()
.code(400)
.message("Bad stuff from the client")
.build()))
https://springfox.github.io/springfox/docs/current/#springfox-spring-mvc-and-spring-boot
Update:
If you want to go the annotation route, you can create your own and place it on your controller.
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#ApiResponses(value = {
#ApiResponse(code = 400, message = "Bad stuff from the client"),
#ApiResponse(code = 404, message = "Item not found") }
)
public #interface GlobalApiReponses {
}
Then use it..
#Api(value = "Title",
description = "What this controller is about"
)
#GlobalApiReponses
public class FooBarController
A combination of approaches might be a good option as well.
The #Target(ElementType.TYPE) means you can apply this at the class level. You can do the same for methods by using the ElemenType.METHOD.
Related
I would like to customize the swaggerUI with some custom messages. And i am able to create custom messages as below
#ApiOperation(value = "Add a new car to the inventory",
nickname = "addCar",
response = String.class,
responseContainer = "String")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Car Added Successfully",
response = String.class, responseContainer = "String"),
#ApiResponse(code = 405, message = "Invalid input") })
But it makes my controller looks really clumsy. Is there any way to customize it without make the controller so noisy.
Thanks
We are creating API documentation for an existing Grails 4 App. We are having difficulties in understanding how to use Swagger annotations.
Let's assume the following Controller:
class IntegratorController {
def maintenanceService
def saveMaintenance() {
def message = 'success'
def status = '200'
try {
def maintenanceJson = request.JSON.maintenances
def ret=maintenanceService.processMaintenanceJSON(maintenanceJson)
} catch (Exception e) {
log.error("Error to process restricions", e)
message = 'error : ${e.getMessage()}'
status = '500'
}
def result = ['message':message]
render(status: status, contentType: "application/json", text: result as JSON)
}
}
This controller expects you to send a request JSON like this example:
{ "job":42,
"maintenances": [
{"idPort":42, "idMaintenance":42, "shipName":"myship01", "obs":"asap"},
{"idPort":43, "idMaintenance":43, "shipName":"myship02", "obs":"asap"}]}
A basic annotation will be this:
#Controller("/")
class IntegratorController {
def maintenanceService
#Post(uri="/saveMaintenance", produces = MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#Operation(summary = "Create one or more ship maintenance")
#ApiResponse(responseCode = "500", description = "If internal service throws an Exception")
def saveMaintenance() {
def message = 'success'
def status = '200'
try {
def maintenanceJson = request.JSON.maintenances
def savedMaintenances=maintenanceService.processMaintenanceJSON(maintenanceJson)
} catch (Exception e) {
log.error("Error to process restricions", e)
message = 'error : ${e.getMessage()}'
status = '500'
}
def result = ['message':message]
render(status: status, contentType: "application/json", text: result as JSON)
}
}
Where and how to annotate the request JSON sent in the post operation?
Thank you!
The request object is "scoped" by Grails. So you need to use #RequestBody annotation to declare what it is outside the method declaration. You also need to create classes to describe what it is because the JSON deserialization is loosely typed.
This is an example:
#Post(uri="/saveMaintenance", produces = MediaType.APPLICATION_JSON)
#Operation(summary = "Summary here",
description = "Description here",
requestBody = #RequestBody(description = "Inside Operation"), tags = ["IntegratorWebController"])
#RequestBody(description = "Description here", required = true,
content = #Content(schema = #Schema(implementation = YourRequestDAO.class, anyOf = [YourRequestDAO.class, YourRequestDAODependency.class])))
#ApiResponses(value=[
#ApiResponse(responseCode="200", description = "Return status=OK in success", content = #Content(mediaType = "application/json", schema = #Schema(implementation = YourResponseDAO.class))),
#ApiResponse(responseCode="404", description = "Return status=BAD_REQUEST if you mess up", content = #Content(mediaType = "application/json", schema = #Schema(implementation = YourResponseDAO.class)))])
def saveOrUpdateActivity(){
(...)
Well Swagger and OpenAPI are 'schemas' that are preloaded at runtime to build the call structure; GraphQL also has a schema as well to load its call structure.
I did a video on it here to help you understand how this works: https://youtu.be/AJJVnwULbbc
The way Grails did this prior to 4.0 was with plugins like the 'swagger plugin' or with BeAPI plugin (which I maintain).
I don't see a supported plugin in 4.0 so I don't see how they are doing this now.
I already went through: How to define an optional parameter in path using swagger.
I've this endpoint:
#ApiOperation(value = "Retrieve Student Data By firstName Or lastName Or middleName",nickname = "Find Student Data")
#ApiResponses(value = { #ApiResponse(code = 200, message = "Successfully Retrieved Student Data"),
#ApiResponse(code = 404, message = "No data found !!!") })
#GetMapping(path = "/firstName/{firstName}/lastName/{lastName}/middleName/{middleName}")
public GetStudentDataResponse getStudentData(#PathVariable(required = false) String firstName, #PathVariable(required = false) String lastName,#PathVariable(required = false) String middleName) {
return service.getStudentData(firstName,lastName,middleName);
}
When I hit the rest endpoint and pass firstName only, Swagger is complaining about required parameter. How can we disabled it ?
Note: I really don't want to create another endpoint just to create / for the sake of to make it working via swagger.
You need to use #RequestParam instead of #PathVariable. Then it allows you to make the Parameters optional.
In my spring boot rest API, I'm sending back a unique request id header "x-request-id" for every response (irrespective of the method) for every endpoint. I can add this using something like this:
#ApiResponses(value = {
#ApiResponse(
code = 200,
message = "Successful status response",
responseHeaders = {
#ResponseHeader(
name = "x-request-id",
description = "auto generated unique request id",
response = String.class)})
})
This works fine and I can see it in the Swagger UI. However, doing this for every endpoint is a tedious + maintenance problem. I'm looking to do this globally but the Springfox documentation only shows about global response message using .globalResponseMessage option - I can't find anything for global response headers.
Ended up creating an annotation to handle this:
package com.abc.xyz.api.docs.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.ResponseHeader;
import com.abc.xyz.api.constants.ApiConstants;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
#ApiResponses(value = {
#ApiResponse(
code = 200,
message = "Successful status response",
responseHeaders = {
#ResponseHeader(
name = ApiConstants.REQUESTIDHEADER,
description = ApiConstants.REQUESTIDDESCRIPTION,
response = String.class)}),
#ApiResponse(
code = 401,
message = "Successful status response",
responseHeaders = {
#ResponseHeader(
name = ApiConstants.REQUESTIDHEADER,
description = ApiConstants.REQUESTIDDESCRIPTION,
response = String.class)}),
#ApiResponse(
code = 403,
message = "Successful status response",
responseHeaders = {
#ResponseHeader(
name = ApiConstants.REQUESTIDHEADER,
description = ApiConstants.REQUESTIDDESCRIPTION,
response = String.class)}),
#ApiResponse(
code = 404,
message = "Successful status response",
responseHeaders = {
#ResponseHeader(
name = ApiConstants.REQUESTIDHEADER,
description = ApiConstants.REQUESTIDDESCRIPTION,
response = String.class)}),
}
)
public #interface RequestIdMethod {};
With this, I can add this as a marker annotation in front of my methods:
#RequestMapping(value = "/heartbeat", method = RequestMethod.GET)
#RequestIdMethod
public Heartbeat checkHeartbeat() {
return new Heartbeat(status);
}
It is not great because I need to repeat the entire #ApiResponse annotation block for every http return code (obviously there could be other return codes but I only covered the default codes shown by Springfox). Would have been better if there was a way to parameterize the entire #ApiResponse block.
I know I'm late to the party here, but I did find a way to globally add a header to every response using reflection (might not be required but turned out to be the easiest way for me to get EVERY response. You can also check for all ApiResponses annotations but some were added implicitly and therefore left out with that approach).
#Component
#Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 10)
public class RequestIdResponseHeaderPlugin implements OperationBuilderPlugin {
#Override
public boolean supports(DocumentationType documentationType) {
return true;
}
#Override
public void apply(OperationContext operationContext) {
try {
// we use reflection here since the operationBuilder.build() method would lead to different operation ids
// and we only want to access the private field 'responseMessages' to add the request-id header to it
Field f = operationContext.operationBuilder().getClass().getDeclaredField("responseMessages");
f.setAccessible(true);
Set<ResponseMessage> responseMessages = (Set<ResponseMessage>) f.get(operationContext.operationBuilder());
responseMessages.forEach(message -> {
int code = message.getCode();
Map<String, Header> map = new HashMap<>();
map.put("my-header-name", new Header(null, null, new ModelRef("string")));
ResponseMessage responseMessage = new ResponseMessageBuilder().code(code).headersWithDescription(map).build();
operationContext.operationBuilder().responseMessages(Collections.singleton(responseMessage));
});
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
Found this way after looking into the method responseMessages() of the operation-builder. It internally merges response-headers based on the status-code and the logic itself will simply add headers to existing response-headers.
Hope it helps someone since it does not require you to annotate every single endpoint.
I updated my Docket configuration to include the Global header on every API. Hope this helps.
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.contact(new Contact("My Support", null, "My Email"))
.description("My Description")
.licenseUrl("My License")
.title("My Title")
.termsOfServiceUrl("My Terms and Conditions")
.version("My Version")
.build())
.globalOperationParameters(Collections.singletonList(new ParameterBuilder()
.name("x-request-id")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build()))
.select()
.paths(PathSelectors.regex("/user*))
.build()
.directModelSubstitute(LocalDate.class, String.class)
.directModelSubstitute(LocalDateTime.class, String.class);
I'm trying to use the Google Machine Learning API and I'm facing two problems.
In the API explorer I put the correct information and I get a response error:
Code 200
"error": "Missing \"instances\" field in request body: {\n \"httpBody\": \n
{\n \"data\": \"\\"instances\\" : \\"teste\\"\",\n
\"contentType\": \"application/json\"\n }\n}"
The request find my model (if I change the value in field name I get another error) but don't understand my json. That's the json:
{"instances" : [{"key":"0", "image_bytes": {"b64": "mybase64"} }]}
When I do the predict on the command line using gcloud, I get no errors and everything seems ok. The Json that I was create for gcloud is a little bit different:
{"key":"0", "image_bytes": {"b64": "mybase64"} }
I already tryied that one in the API explorer and no success.
So, I decided to use the .Net Api to try the predict and I get other situation: The Response is Empty (???).
Here is my code:
'get the service credential that I created
Dim credential = Await GetCredential()
Dim myService As New CloudMachineLearningEngineService(New BaseClientService.Initializer() With {
.ApplicationName = "my Project Name (Is That It???)",
.ApiKey = "my API Key",
.HttpClientInitializer = credential
})
Dim myBase64 As String = GetBase64("my image path to convert into a base64 String")
Dim myJsonRequest As String = "{""instances"" : [{""key"":""0"", ""image_bytes"": {""b64"": """ + myBase64 + """}}]}"
Dim myRequest = New GoogleCloudMlV1PredictRequest With {
.HttpBody = New GoogleApiHttpBody With {.Data = myJsonRequest,
.ContentType = "application/json"
}
}
'If I change the model name I get error
Dim myPredictRequest = myService.Projects.Predict(myRequest, "projects/myProject/models/myModel/versions/v1")
myPredictRequest.AccessToken = credential.Token.AccessToken
myPredictRequest.OauthToken = credential.Token.AccessToken
myPredictRequest.Key = "my API Key
'Execute the request
Dim myResponse = myPredictRequest.Execute()
'at this point, myResponse is Empty (myResponse.ContentType Is Nothing, myResponse.Data Is Nothing And myResponse.ETag Is Nothing)
If I change the model name I get a error informing that my model was not found, so my credentials are right.
I don't know what I'm doing wrong. Someboby can help with any of this issues?
Thanks!
UPDATE: --------------------------
I changed this Execute Command:
Dim myResponse = myPredictRequest.Execute()
To This One:
Dim s = StreamToString(myPredictRequest.ExecuteAsStream())
and Now I can get the same error with .Net API and google developers interface (Missing instances field...).
So If someboby just Know what is wrong with my Json request, It will help a lot.
The JSON you put in the API explorer is indeed correct (assuming, of course, your model has inputs key and image_bytes). This appears to be a bug with the explorer I will report.
The reason you are getting the error you are in the .NET code is because you are using an .HttpBody field. This code:
Dim myJsonRequest As String = "{""instances"" : [{""key"":""0"", ""image_bytes"": {""b64"": """ + myBase64 + """}}]}"
Dim myRequest = New GoogleCloudMlV1PredictRequest With {
.HttpBody = New GoogleApiHttpBody With {.Data = myJsonRequest,
.ContentType = "application/json"
}
}
Will produce a JSON request that looks like this:
{
"httpBody": {
"data": "{\"instances\" : [{\"key\":\"0\", \"image_bytes\": {\"b64\": \"mybase64\"} }]}",
"contentType": "application\/json"
}
}
When what you really need is:
{"instances" : [{"key":"0", "image_bytes": {"b64": "mybase64"} }]}
Hence the error message you see.
I don't know how to generate the correct response using the .NET library; based on the Python example in the docs, I would guess:
Dim myJsonRequest As String = "{""instances"" : [{""key"":""0"", ""image_bytes"": {""b64"": """ + myBase64 + """}}]}"
Dim myPredictRequest = myService.Projects.Predict(myJsonRequest, "projects/myProject/models/myModel/versions/v1")
But I don't have a good way of testing that. For reference, the Python equivalent is:
response = service.projects().predict(
name=name,
body=myJsonRequest
).execute()
I solved the problem with .Net API.
I created two new classes Inherits the Google API's classes.
Something like that:
Imports Google.Apis.CloudMachineLearningEngine.v1.Data
Imports Newtonsoft.Json
Public Class myGoogleCloudMlV1PredictRequest
Inherits GoogleCloudMlV1PredictRequest
<JsonProperty("instances")>
Public Property MyHttpBody As List(Of myGoogleApiHttpBody)
End Class
Imports Google.Apis.CloudMachineLearningEngine.v1.Data
Imports Newtonsoft.Json
Public Class myGoogleApiHttpBody
Inherits GoogleApiHttpBody
<JsonProperty("image_bytes")>
Public Property MyData As image_byte
<JsonProperty("key")>
Public Property key As String
End Class
So, in my original code I change this part:
Dim myBase64 As String = GetBase64("my_image_path_to_convert_into_a _base64_String")
Dim myJsonRequest As String = "{""instances"" : [{""key"":""0"", ""image_bytes"": {""b64"": """ + myBase64 + """}}]}"
Dim myRequest = New GoogleCloudMlV1PredictRequest With {
.HttpBody = New GoogleApiHttpBody With {.Data = myJsonRequest,
.ContentType = "application/json"
}
}
For this one:
Dim myBase64 As String = GetBase64("my_image_path_to_convert_into_a _base64_String")
Dim myRequest = New myGoogleCloudMlV1PredictRequest With {
.MyHttpBody = New List(Of myGoogleApiHttpBody)()
}
Dim item As myGoogleApiHttpBody = New myGoogleApiHttpBody With {
.key = "0",
.MyData = New image_byte With {
.b64 = myBase64
}
}
myRequest.MyHttpBody.Add(item)
And voilá, It's working!
Thanks for everyone!!
Github issue #1068 shows two work-arounds for this problem.
In summary, use service.ModifyRequest to insert the raw JSON content.
Or use service.HttpClient.PostAsync(...) directly.