I describe a path in openapi file in my spring boot application.
Openapi generates by the file api class which handles http requests.
Also I use swagger which hepls construct a valid url, where I can put query parameters as well.
I'm wondering, why having all this generated staff I receive null object instead of expected.
part of api.yaml
/films:
get:
summary: Отфильтрованные фильмы
operationId: findFilms
tags: [selections]
parameters:
- in: query
name: filter
schema:
type: object
properties:
genreId:
type: integer
year:
type: integer
countryId:
type: integer
style: deepObject
explode: false
responses:
200:
description: successfull response
content:
application/json:
schema:
$ref: 'list-schemas.yaml#/components/schemas/SelectionTo'
generated java class
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
#Validated
#Api(value = "Selections")
public interface SelectionsApi {
default Optional<NativeWebRequest> getRequest() {
return Optional.empty();
}
#ApiOperation(value = "Отфильтрованные фильмы", nickname = "findFilms", , response = SelectionTo.class, tags={ "selections", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "successful response", response = SelectionTo.class) })
#RequestMapping(value = "/films",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<SelectionTo> _findFilms(#ApiParam() #Valid #RequestParam(value = "filter", required = false) Filter filter) {
return findFilms(filter);
}
// Override this method
default ResponseEntity<SelectionTo> findFilms(Filter filter) {
getRequest().ifPresent(request -> {
...
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
generated query parameter class
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
public class Filter {
#JsonProperty("genreId")
private Integer genreId = null;
#JsonProperty("year")
private Integer year = null;
#JsonProperty("countryId")
private Integer countryId = null;
public Filter genreId(Integer genreId) {
this.genreId = genreId;
return this;
}
implementing interface
#Override
public ResponseEntity<SelectionTo> findFilms(Filter filterType) {
//here filter is null !
return ResponseEntity.ok(transformer.transform(service.getItemsInfo()));
}
request
http://localhost/films?filter[genreId]=13&filter[year]=2021
How openapi file could be improved? Because this is the only thing I've defined. Or what else could the reason?
As far as I can see, Spring MVC does not support decoding nested object query parameters in the OpenAPI deepObject style, like filter[genreId]=13, at least out of the box.
Try to remove #RequestParam() from filter object.
Like this:
default ResponseEntity<SelectionTo> _findFilms(#ApiParam() #Valid Filter filter) {
return findFilms(filter);
}
Also the request should be http://localhost/films?genreId=13&year=2021
Related
I'm using SpringBoot with the following dependency
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.12</version>
</dependency>
The controller class (#RestController) has one entry point (#GetMapping), and this entry point should return a List of the object : MyClass.java. I added Swagger annotations above the method in order to create API documentation via a swagger UI page.
The swagger documentation should indicate that the return object is of type
List< MyClass>
But how should I do that ? If I do
"#Schema(implementation = List< MyClass >.class)"
there is a compile error.
Swagger annotations:
#Operation(....)
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = "successful operation",
content = { #Content(mediaType = "application/json",
schema = #Schema(implementation = ????)) }),
#ApiResponse(...),
#ApiResponse(...)
#GetMapping(value = "/aaa", produces = MediaType.APPLICATION_JSON_VALUE)
public List<MyClass> getAaa(...)
{
return ...
}
You need to use an ArraySchema annotation for this and assign it to the array attribute instead of the schema attribute of the #Content annotation.
You don't need to specify List.class only its type parameter MyClass.class.
#Operation(
summary = "Get a list of users",
description = "Get a list of users registered in the system",
responses = {#ApiResponse(
responseCode = "200",
description = "The response for the user request",
content = {
#Content(
mediaType = "application/json",
array = #ArraySchema(schema = #Schema(implementation = User.class))
)
})
}
)
#GET
#SecurityRequirement(name = "JWT")
#Path("/user")
public List<User> getUsers() {
return null;
}
I want to respond with different result objects in a Swagger generated API. The type of object is dependent on the result code.
But it seems that the Swagger codegen generates only code that allows the first defined/used type to be returned.
An example Swagger definition that returns different objects in the OK and error case is like:
swagger: "2.0"
info:
description: "API"
version: 1.0.0
title: Example
host: localhost:8080
schemes:
- http
paths:
/exampleCall:
get:
operationId: exampleCall
produces:
- application/json
responses:
200:
description: OK
schema:
$ref: '#/definitions/exampleResponse'
400:
description: Error
schema:
$ref: '#/definitions/exampleError'
definitions:
exampleResponse:
type: object
properties:
result:
type: string
exampleError:
type: object
properties:
code:
type: string
This then gets generated by the SwaggerCodeGen into following API interface
#Validated
#Api(value = "exampleCall", description = "the exampleCall API")
#RequestMapping(value = "")
public interface ExampleCallApi {
ExampleCallApiDelegate getDelegate();
#ApiOperation(value = "", nickname = "exampleCall", notes = "", response = ExampleResponse.class, tags={ })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "OK", response = ExampleResponse.class),
#ApiResponse(code = 400, message = "Error", response = ExampleError.class) })
#RequestMapping(value = "/exampleCall",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<ExampleResponse> exampleCall() {
return getDelegate().exampleCall();
}
}
But when I try to implement the delegate like this
public class ExampleCallApiDelegateImpl implements ExampleCallApiDelegate {
#Override
public ResponseEntity<ExampleResponse> exampleCall() {
ExampleError error = new ExampleError();
error.setCode("123");
return new ResponseEntity<ExampleError>(error, HttpStatus.BAD_REQUEST);
}
}
it of course fails to compile because of incorrect return types.
What would be the proper way to implement different return objects per response code with that Swagger generated API?
Is there even a proper way?
Requirement: I have a POST method which takes the input JSON as a String and passes it to another microservice. I don't want to create an Object (Bean) of this input JSON.
method:
#ApiOperation(notes = "example" value = "/example", consumes = ".." , method= "..")
#RequestMapping(name = "xxx" value ="/hello" ..)
#ApiResponses(..)
public #ResponseBody String getXXX (#Apiparam(name="JSONrequest", required = true) #RequestBody String JSONrequest){
}
Problem:
The generated Swagger doesn't show the input as a JSON model where all the JSON attributes are displayed.
Expectation:
I want to display my Swagger Something like this :
Definately I am missing the key thing. Any thoughts?
If changing from String to a concrete object is not okay (although that's what I would recommend you to do since it's cleaner), you can try using #ApiImplicitParams (check out their documentation)
#ApiOperation(notes = "example" value = "/example", consumes = ".." , method= "..")
#ApiImplicitParams({
#ApiImplicitParam(name = "Object", value = "Object to be created", required = true, dataType = "your.package.BodyClass", paramType = "body")
})
#RequestMapping(name = "xxx" value ="/hello" ..)
#ApiResponses(..)
public #ResponseBody String getXXX (#Apiparam(name="JSONrequest", required = true) #RequestBody String JSONrequest){
}
(not sure if you still need the #Apiparam(name="JSONrequest", required = true) bit from the method parameter)
It's an old question but since I haven't found a solution online here how I to customized the example value in the swagger documentation produce automatically by the java annotations.
I use swagger 2.0 and springfox.version 2.10.5.
The Idea is documenting the class of the request parameter that has the #RequestBody annotation. for example my method is
#ApiOperation(
value = "Start ListBuilder extraction",
response = ExtractionLogEntity.class,
produces = "application/json"
)
#PostMapping("/extraction/start")
public ExtractionLogEntity startTask(
#RequestBody(required = true) ExtractionRequest request,
In order to expose request json object example I added a #ApiModelProperty(example = "...") annotation to the properties of ExtractionRequest .
#ApiModelProperty(example = "[{ 'field':'value'}]")
#NotNull
private List<ListBuilderFieldEntity> fields;
#ApiModelProperty(example = "1000")
private String ied;
#ApiModelProperty(example = "US")
private String codebase;
And that's the result
I had the similar issue. My Service Class takes #RequestBody argument in String.
So, what I did :
Created a POJO and used #RequestBody annotation with it instead of inputString.
#RequestMapping(value = "/api/entity/{entityId}/user/query", method = {RequestMethod.POST}, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody
ResponseEntity<String> queryUser(#PathVariable("entityId") String entityId,
#RequestBody QueryUserJsonSchemaPOJO queryUserJsonSchemaPOJO, String inputString,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
return userService.queryUserService(inputString, entityId, request);
}
Created an AOP with #Around annotation which update the inputString argument.
#Around(value = "execution(* com.athmin.rest.UserController.*(..)) || execution(* com.athmin.rest.CityController.*(..)), and args(..) " +
" && #annotation(com.athmin.annotations.JSONSchemaFileName) ")
public Object validateRequestBodyAgainstJsonSchema(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object[] modifiedArgs = proceedingJoinPoint.getArgs();
for (Object o : proceedingJoinPoint.getArgs()) {
if (o instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) o;
requestBody = httpServletRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
}
});
for (int i = 0; i < modifiedArgs.length; i++) {
if (modifiedArgs[i] == null) { // Only inputString is null in my case
modifiedArgs[i] = requestBody;
}
}
proceedingJoinPoint.proceed(modifiedArgs);
}
There isa problem, when we use Jersey2 resource with implicitly dto usage.
Example:
#POST
#ApiOperation(value = "Create pet", response = PetDTO.class)
public Pet create(Pet pet) throws IOException {
return this.petService.save(pet);
}
In this example we implicitly get petDto as param, and than map it to entity.
The question is, is the way to how to configure swagger to document PetDTO as api param, not Pet?
It can be done next way:
#POST
#ApiOperation(value = "Create pet", response = PetDTO.class)
#ApiImplicitParams({
#ApiImplicitParam(name = "Pet dto",
value = "pet", required = true,
dataType = "com.example.PetDTO", paramType = "body")
})
public Pet create(#ApiParam(hidden = true) Pet pet) throws IOException {
}
I'm developing a Web App using Spring 4 MVC. I want to know If I can validate JSON request objects with javax.validation API. For example I have this chunk of my entity code:
...
#JsonProperty("cheFecha")
#NotNull
#Column(name = "che_fecha")
#Temporal(TemporalType.DATE)
#DateTimeFormat(style = "M-")
private Date SsiCheque.cheFecha;
#JsonProperty("cheMonto")
#NotNull
#JsonSerialize(using = CurrencySerializer.class)
#Column(name = "che_monto", precision = 10, scale = 2)
private BigDecimal SsiCheque.cheMonto;
...
I have the controller code:
#RequestMapping(value = "/addCheck", method = RequestMethod.POST)
public #ResponseBody SsiCheque addChecks(#Valid SsiCheque ssiCheque, BindingResult result) {
//ssiCheque.persist();
System.out.println("add" + result.getErrorCount());// Zero when there are errors
return ssiCheque;
}
And finally I have the jQuery code:
var formData = $("#formAddChecks :input").serializeArray();
$.ajax({
type: "POST",
url: "addCheck",
data: formData,
beforeSend: function ( xhr ) {
console.log("before Send");
},
error: function (request, status, error) {
console.log('Error ' + "\n" + status + "\n" + error);
},
success: function(data) {
console.log(data);
}
});
The JSON object is arriving correctly to the controller but I want to validate the JSON with the entity javax.annotations API. What I have seen is only using custom validators and "rewrite" the validation code.
Is this the only way to validate JSON?
Thanks in advance!
UPDATE 1
I followed the #James Massey suggestions and my code looks like this right now:
Controller
#RequestMapping(value = "/addCheck", method = RequestMethod.POST)
#ResponseBody
public SsiCheque addChecks(#Valid #RequestBody SsiCheque ssiCheque, BindingResult result) {
//ssiCheque.persist();
System.out.println("agregar " + result.getErrorCount());
return ssiCheque;
}
Javascript file
var ssiCheque = {
cheNumero : $("#formAddChecks cheNumero").val(),
cheRecepto : $("#formAddChecks cheReceptor").val(),
cheMonto : $("#formAddChecks cheMonto").val(),
cheFecha : $("#formAddChecks cheFecha").val(),
cheConcepto : $("#formAddChecks cheConcepto").val()
};
$.ajax({
type: "POST",
contentType: "application/json",
url: "addCheck",
data: ssiCheque,
dataType: "json",
beforeSend: function ( xhr ) {
console.log("before Send");
},
error: function (request, status, error) {
console.log('Error ' /*+ request.responseText*/ + "\n" + status + "\n" + error);
},
success: function(data) {
console.log(data);
}
});
But I'm getting an 400 Error (Incorrect request) when I submit the form and execute the Ajax function. I have faced this error before when the json object format and the controller specs were incompatible, but in this time I don't know why can be the error.
Thanks again!
I have solved my validations in another way. Suppose I have and Agent Object:
public class Agent {
public int userID;
public String name;
public boolean isVoiceRecorded;
public boolean isScreenRecorded;
public boolean isOnCall;
}
I would like to validate :
(1) userID>0
(2) name is mandatory
(3) isVoiceRecorded and isScreenRecorded can be true only if isOnCall is true.
In order to do so I need to add dependency :
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
Now look how Agents class looks like:
#NoArgsConstructor
#ToString
#EqualsAndHashCode(of = "userID")
#CheckBools
public class Agent {
#Min(0)
public int userID;
#NotNull(message = "Name cannot be null")
public String name;
public boolean isVoiceRecorded;
public boolean isScreenRecorded;
public boolean isOnCall;
public LocalDateTime startEventDateTime;
}
(1) #Min(0) - solves userID>0
(2) #NotNull(message = "Name cannot be null") - solves name is mandatory, and you have example how to specify error message
(3) #CheckBools annotation defined by me, at the class level which checks isVoiceRecorded and isScreenRecorded can be true only if isOnCall is true.
#Documented
#Constraint(validatedBy = MyConstraintValidator.class)
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
public #interface CheckBools {
String message() default "'isVoiceRecorded' or 'isScreenRecorded' can be true only if you are on call";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
In the following class you define the rule
public class MyConstraintValidator implements ConstraintValidator<CheckBools, Agent> {
#Override
public void initialize(CheckBools constraintAnnotation) {
}
#Override
public boolean isValid(Agent value, ConstraintValidatorContext context) {
if (!value.isOnCall && (value.isVoiceRecorded || value.isScreenRecorded))
return false;
else return true;
}
}
At the controller level :
#RestController
#RequestMapping("Myteamview")
public class MyteamviewController {
#Autowired
AgentInfo agentInfo;
#RequestMapping(path = "agents", method = RequestMethod.POST)
public ResponseEntity<Boolean> addOrUpdateAgent(#Valid #RequestBody Agent agent) {
ResponseEntity<Boolean> responseEntity = new ResponseEntity<>(agentInfo.addAgent(agent),HttpStatus.OK);
return responseEntity;
}
}
Note: The important is that you specify #Valid before #RequestBody Agent
There appear to be a few problems here:
Your object structure seems weird. Why are your fields referencing an object type? private Date SsiCheque.cheFecha seems to be a totally non-sensical field.
You generally design your UI to send through a JSON object that can be mapped directly into your Java object. So if your object looked like this:
public class Example {
#NotNull
#Digits(fraction = 2, integer = 10)
private Integer foo;
#NotEmpty
private String bar;
#NotEmpty
private String[] baz;
}
Then your JSON structure would be something like this:
{
"example": {
"foo": 1,
"bar": "Pineapple",
"baz": [
"This is a string",
"So is this"
]
}
}
Which can be used by Jackson to map straight into your object.
You would then write your controller method like this assuming that you had the Jackson JAR included in your project classpath:
#RequestMapping(value = "/example", method = RequestMethod.POST)
#ResponseBody
public Example(#Valid #RequestBody Example example, BindingResult result) {
if(result.hasErrors()){
//A validation has failed, return an error response to the UI
} else {
exampleService.createOrUpdate(example);
return example;
}
}
The important part is that your object is the request body and you use the #RequestBody annotation, as Jackson uses this as a signal to construct your object using the JSON present in your HTTP Request Body. The only downside to this method is that you may have to construct your request JSON programmatically. This is trivial to do with JavaScript however.
(I'm going to assume some sensible input id defaults here, and that you are familiar with the jQuery DOM manipulation/selection syntax)
var bazArray = [];
$.forEach($("#bazContainer"), function (baz, i){
bazArray.push(baz);
});
var example = {
foo: $("#fooInput").val(),
bar: $("#barInput").val(),
baz: bazArray
};
You pass in your example object to your request in the data field, and if you specify that it is of type application/json then jQuery will automatically call JSON.stringify on your example object.
Hopefully this all makes sense.
SOLUTION (Updated by questioner: Jessai)
I checked this question: Spring MVC 400 Bad Request Ajax.
In summary what I did:
Create an object to be parsed with JSON.stringify and send it to the controller.
In the controller I set the method with #ResponseBody and #RequestBody as #James Massey said.
In the entity I added #JSONProperty (I had these already) and #JSONIgnore (I added to cheId field) annotations to the fields.
Javascript:
var ssiCheque = {
cheNumero : $("#formAddChecks #cheNumero").val(),
cheRecepto : $("#formAddChecks #cheReceptor").val(),
cheMonto : $("#formAddChecks #cheMonto").val(),
cheFecha : $("#formAddChecks #cheFecha").val(),
cheConcepto : $("#formAddChecks #cheConcepto").val()
};
$.ajax({
type: "POST",
contentType: "application/json",
url: "addCheck",
data: JSON.stringify(ssiCheque),
dataType: "json",
beforeSend: function ( xhr ) {
console.log("before Send");
},
error: function (request, status, error) {
console.log('Error ' /*+ request.responseText*/ + "\n" + status + "\n" + error);
},
success: function(data) {
console.log(data);
}
});
Controller
#RequestMapping(value = "/addCheck", method = RequestMethod.POST)
#ResponseBody
public SsiCheque addChecks(#Valid #RequestBody SsiCheque ssiCheque, BindingResult result) {
//ssiCheque.persist();
System.out.println("agregar " + result.getErrorCount());
return ssiCheque;
}
Thanks!