I'm using Spring Boot v2.6.0 to create a simple hosting service.
For Swagger I'm using SpringFox version 3.0.0, and Swagger2.
As I've never used Swagger or OpenAPI inside my Spring projects I'm out of ideas.
I'm having an issue with this method:
#CrossOrigin
#Operation(summary = "Check if a resourcepack exists with the specified id.")
#ApiResponse(responseCode = "200", description = "Resourcepack found", content = {
#Content(mediaType = "application/json", schema = #Schema(implementation = ExistsResponse.class))
})
#ApiResponse(responseCode = "400", description = "Bad Request", content = {
#Content(mediaType = "application/json", schema = #Schema(implementation = ErrorResponse.class))
})
#ApiResponse(responseCode = "403", description = "Blacklisted IP", content = {
#Content(mediaType = "application/json", schema = #Schema(implementation = ErrorResponse.class))
})
#ApiResponse(responseCode = "429", description = "Too Many Requests (rate-limited)", content = {
#Content(mediaType = "application/json", schema = #Schema(implementation = RateLimitResponse.class))
})
#ApiResponse(responseCode = "500", description = "Internal Error (unseen exceptions)", content = {
#Content(mediaType = "application/json", schema = #Schema(implementation = ErrorResponse.class))
})
#GetMapping(value = "/api/exists/{id:.+}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ExistsResponse> exists(#Parameter(description = "Id of the resourcepack to be checked") #PathVariable String id, HttpServletRequest request) throws InternalErrorException {
I get the following error when navigating to swagger-ui, for all models except the ExistsResponse one.
2022-08-05 17:29:18.057 ERROR 17372 --- [nio-8080-exec-1] nceModelSpecificationToPropertyConverter : Unable to find a model that matches key ModelKey{qualifiedModelName=ModelName{namespace='net.iceyleagons.resourcehost.responses', name='ErrorResponse'}, viewDiscriminator=null, validationGroupDiscriminators=[], isResponse=true}
2022-08-05 17:29:18.058 ERROR 17372 --- [nio-8080-exec-1] nceModelSpecificationToPropertyConverter : Unable to find a model that matches key ModelKey{qualifiedModelName=ModelName{namespace='net.iceyleagons.resourcehost.responses', name='ErrorResponse'}, viewDiscriminator=null, validationGroupDiscriminators=[], isResponse=true}
2022-08-05 17:29:18.058 ERROR 17372 --- [nio-8080-exec-1] nceModelSpecificationToPropertyConverter : Unable to find a model that matches key ModelKey{qualifiedModelName=ModelName{namespace='net.iceyleagons.resourcehost.responses', name='RateLimitResponse'}, viewDiscriminator=null, validationGroupDiscriminators=[], isResponse=true}
2022-08-05 17:29:18.059 ERROR 17372 --- [nio-8080-exec-1] nceModelSpecificationToPropertyConverter : Unable to find a model that matches key ModelKey{qualifiedModelName=ModelName{namespace='net.iceyleagons.resourcehost.responses', name='ErrorResponse'}, viewDiscriminator=null, validationGroupDiscriminators=[], isResponse=true}
I'm using Lombok on my models. As an example here is my RateLimitResponse class:
#Data
#RequiredArgsConstructor
public class RateLimitResponse {
private final long refill;
private final String error;
public static RateLimitResponse from(RateLimitedException e) {
return new RateLimitResponse(e.getRefill(), e.getMessage());
}
}
As a comparison here's the ExistsResponse:
#Data
#RequiredArgsConstructor
public class ExistsResponse {
private final String downloadUrl;
private final boolean exists;
private final long available;
private final long remainingTokens;
public static ExistsResponse found(String downloadUrl, long available, long remainingTokens) {
return new ExistsResponse(downloadUrl, true, available, remainingTokens);
}
public static ExistsResponse empty(long remainingTokens) {
return new ExistsResponse("", false, -1, remainingTokens);
}
}
I have no idea why this does not affect the ExistsResponse model, as it's constructed in a totally equal way.
There's a few things wrong here with your code here.
multiple #ApiResponse objects not wrapped
According to the swagger documentation for #ApiResponses, the #ApiResponse object must be wrapped in the responses wrapper. The #ApiResponse documentation says "This annotation is not used directly and will not be parsed by Swagger. It should be used within the ApiResponses."
Controller Advice
You should have an #ControllerAdvice class to manage all exceptions. There's a great article on how to do this here, but feel free to find your own.
Method Signature
Your method signature is ResponseEntity<ExistsResponse>. It can't return another response (unless you define it in #ControllerAdvice). Make this generic if possible, like ResponseEntity<?>.
A couple of other notes:
#Data already includes the #RequiredArgsConstructor, so there's no need to add that annotation here.
The #RequiredArgsConstructor makes a contructor consisting of fields marked with #NonNull. In your case, since you don't have fields annotated this way, then your #RequiredArgsConstructor is basically acting the same as a #NoArgsConstructor.
Java automatically creates a NoArgsConstructor at compile time for a class with no constructors. There's no need to declare the #RerquiredArgsConstructor unless you are also declaring an #AllArgsConstructor and/or an #NoArgsConstructor.
#Data does a lot of stuff. If you are only using it as a simple object, maybe consider #Value instead.
As I was searching through the internet I found, that SpringFox's current version doesn't really support #ControllerAdvice (or rather Exceptions) and there's an open issue related to this on GitHub. (https://github.com/springfox/springfox/issues/521)
I was able to fix my problem by using springdoc instead of Springfox.
Related
I'm building a REST API with Spring; well currently I'm failing to do so.
TL;DR
I get either this (error 1)
JSON parse error: Could not resolve type id 'test1' as a subtype of crm.zappes.core.template.domain.model.TemplateRequest: known type ids = [TemplateRequest]
or this (error 2)
JSON parse error: Root name ('test1') does not match expected ('TemplateRequest') for type crm.zappes.core.template.domain.model.TemplateRequest
Model
I used #JsonTypeInfo to wrap the class name around it; that leads to error 1.
{"TemplateRequest":{"test1":"Anakin","test2":"Skywalker"}}
If I use the default without this annotation the generated JSON doesn't have a wrapping root element which leads to error 2:
{"test1":"Anakin","test2":"Skywalker"}
#Data #Builder #NoArgsConstructor #AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
// With this I get error 1, without it error 2
#JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
public class TemplateRequest {
private String test1;
private String test2;
}
Controller
In this Controller Endpoint I want the JSON to be converted into a TemplateRequest Model Object.
#RestController
#RequestMapping("/zappes/")
public class TemplateController {
#PostMapping(value = "/template/test", consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<String> testPost(#RequestBody TemplateRequest request) {
return ResponseEntity.ok("Hello World");
}
}
If I change it to #RequestBody String request it works fine and I see the 2 JSON variants (see above), so the endpoint mapping itself works. Spring just cannot parse the JSON into a model object. Which is kind of weird, because the JSON was also generated by the Spring REST framework. See next section.
Test
Here I'm sending the POST Call to the Controller.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class TemplateControllerIntegrationTests {
#Test
void testPost() {
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth("server_user", "server_password");
var request = TemplateRequest.builder().test1("Anakin").test2("Skywalker").build();
var requestEntity = new HttpEntity<>(request, headers);
var restTemplate = new RestTemplate();
var result = restTemplate.exchange("http://localhost:8083/zappes/template/test", HttpMethod.POST, requestEntity, String.class);
Assertions.assertEquals("Hallo Welt", result.getBody());
}
}
Apparently there is no default JSON Converter to I needed to create a little Configuration class:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
var jsonConverter = new MappingJackson2HttpMessageConverter();
var objectMapper = new ObjectMapper();
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
I have application in Spring and creating documentation for this in OpenAPI with annotations for controllers methods. For example I have method getById (simplified for readability):
#GetMapping("/{id}")
#ApiResponse(
responseCode = "200",
description = "Successful operation.",
content = #Content(
mediaType = "application/json",
schema = #Schema(implementation = ScheduleResponse.class)
)
)
#ApiResponse(
responseCode = "404",
description = "The object with the specified ID does not exist in the system.",
content = #Content(
mediaType = "application/json",
schema = #Schema(implementation = ApiError.class)
)
)
ScheduleResponse getById(#PathVariable Long id) throws EntityNotFoundException;
For 404 NOT_FOUND I returns my own ApiError with list of ApiErrorDetails interface:
#Getter
public class ApiError {
private final LocalDateTime timestamp;
private final String status;
private final String message;
private List < ApiErrorDetails > details;
}
public interface ApiErrorDetails {
}
In that case, I'm using a specific implementation of the interface:
#Getter
public class EntityNotFoundDetails implements ApiErrorDetails {
private final String field;
private final Object notFoundValue;
}
With the above implementation, I get JSON in the documentation with no specific field information inside details for example:
and for schema:
Instead, I'd like to prepare an example like this:
{
"timestamp": "2021-08-08T13:32:10.875Z",
"status": "string",
"message": "string",
"details": [
{
"field": "string",
"notFoundValue": {}
}
]
}
Of course, I need solution for that specific case. This means that I don't want to add the
#Schema(example = "value")
to the details list because I provide different implementations in different cases.
I found a solution that is not perfect but sufficient for documentation purposes.
All that is needed is to add #Schema annotation with the property oneOf over ApiErrorDetails. For example for two interface implementations: EntityNotFoundDetails and ValidationErrorDetails:
#Schema(oneOf = {EntityNotFoundDetails.class, ValidationErrorDetails.class})
interface ApiErrorDetails {
}
In the documentation, it looks like this:
which suggests a slightly different shape of JSON than in reality, but the schema tab dispels doubts:
Probably the only way to provide one implementation of your choice is to simply use different classes and not extend the interface.
Here is my controller request
#PostMapping("/requestApproval")
#PreAuthorize("hasRole('USER')")
public ResponseEntity<MessageResponse> requestApproval(#DTO(TripIdDTO.class) Trip requestingApprovalTrip) {
this.tripService.requestApproval(requestingApprovalTrip);
return ResponseEntity.ok().body(new MessageResponse("Trip status has been changed to WAITING_FOR_APPROVAL!"));
}
The annotation takes the request body from JSON format, converts it into DTO and than into the Trip entity type.
Swagger generates the parameters using the fields of the Trip entity. Is there a way to customize swagger to use the TripIdDTO class to create the parameters for the documentation isntead of Trip?
Since the project doesn't obey the usual contract between Swagger and Spring Boot, some additional settings should be done to make it work as wish.
Step 1 Register the real API model
#Configuration
// Only need for swagger 2.9.2
#EnableSwagger2
public class SpringFoxConfig {
#Bean
public Docket api() {
TypeResolver resolver = new TypeResolver();
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build().additionalModels(resolver.resolve(MessageDto.class));
}
}
Step 2 Tell Swagger the real API model used
#RestController
public class TestController {
#RequestMapping(value = "/message",
produces = {"application/json;charset=utf-8"},
consumes = {"application/json;charset=utf-8"},
method = RequestMethod.POST)
#ApiImplicitParams({
#ApiImplicitParam(name = "request", required = true,
dataType = "MessageDto", paramType = "body")
})
ResponseEntity<?> createMessage(Message message) {
return null;
}
}
By doing this, we declare a param with the type MessageDto and should be fetched from the HTTP request body.
Step 3 Tell Swagger to ignore the existed params
#Data
public class Message {
#ApiModelProperty(hidden = true)
private Integer code = null;
#ApiModelProperty(hidden = true)
private String message = null;
}
The Message class has two filed with public get methods(I think it's also your case). Since there is no #RequestBody besides Message, Swagger will treat all fields of Message as query params. To make these useless parts invisible in the API doc, we should mark them as hidden.
P.S.
This function works properly in Swagger 2.9.2, and not work in 3.0.0. It is a bug and this needs some time to get fixed I guess. You can find more information in springfox issue #3435.
All the codes can be found at this repo - swagger demo.
Current look of Swagger request
Is it possible to change the body
{
"config": {
"additionalProp1": {},
"additionalProp2": {},
"additionalProp3": {}
},
"class": "string"
}
to look like
{
"class": "my.class.com",
"config": {
"myParam": "",
"datacenter": "USA",
}
Currently the method looks like this
#POST
#Path("/run")
#Consumes(MediaType.APPLICATION_JSON)
#ApiOperation(value = "Runs a specified job asynchronously",
notes = "Doesn't work for full tasks.")
public Response createTask(TaskConfig taskConfig) {
TaskConfig is a basic class with two member variables
public class TaskConfig {
#JsonProperty("class")
#NotNull
private String clazz;
private Map<String, Object> config;
We are using Swagger 1.5 from this Dropwizard Swagger bundle library. I know that 2.0 has the #RequestBody annotation and I just want to make sure that is my only option before I go down the path of upgrading.
Even by using #RequestBody we need to add additional annotation within TaskConfig class.
We are using Swagger 2.0.x. The TaskConfig class would have fields something like this :
#Schema(
description = " My descriptions",
type = "array",
example = " {\"myParam\" :\"value\" ,"
+ "\"datacenter\": \"USA\"}")
private Map<String, Object> config;
Similarly clazz can also be annotated like :
#Schema(description = "The field descrition", example = "true")
And RequestBody annotation will look like :
#RequestBody(
description = "Description of TaskConfig ",
content = #Content(schema = #Schema(implementation = TaskConfig .class)))
I got the result as shown in the image:
I am using Swagger with SprintBoot to generate the endpoints documentation, it is working great with one exception: I have a POST endpoint with a MultipartFile parameter. In this case Swagger generates the documentation but not the Example Value (it is empty).
public String create(#ApiParam(value = "Record to be created", required = true, type = "json", format = "json")
#RequestPart(name = "candidate") MyDto record,
#ApiParam(value = "File associated to the record", required = false)
#RequestPart(value = "file", required = false) MultipartFile file) throws Exception
Without the MultipartFile the Example Value shows the JSON example which can be used. I would like to have the same when an additional (optional) MultipartFile parameter is included.
Can this be addressed somehow?
as i know swagger docs can be placed when adding annotations something like this:
#ApiModel(value="MyFile")
public class MyFile{
#ApiModelProperty(value = "originalFileName", example="The original filename")
private String getOriginalFilename;
[...]
}
I would suggest to extend the MultipartFile Object and add this Annotations. So you can add documentation to the params and your optional params too.
P.S. MultipartFile is an Interface so you have to extend one of the Implementations e.g. CommonsMultipartFile. Than you have to include the org.apache.commons.fileupload dependency to your project (for FileItem).