JsonView class with Swagger #ApiResponse - java

I have a #JsonView named BankAccountView.Public who help me to restrict some fields in BankAccount because I don't want to send all their attributes in a public get operation. My issue is when I try to specify it using swagger because if I specify BankAccount.class it shows the entire object instead of all the fields specify in my #JsonView, but if I specify BankAccount.Public.class it show me an empty object. Could you please tell me if it is possible that Swagger shows only the public fields?
Here is my code:
// BankAccount Json View
public class BankAccountView {
public static class Public {}
}
// BankAccount class
#ApiModel("BankAccount")
public class BankAccount {
#ApiModelProperty
#JsonView(BankAccountView.Public.class)
private Long accountId;
#ApiModelProperty
private Long owner;
#ApiModelProperty
#NotBlank
#JsonView(BankAccountView.Public.class)
private String currency;
#ApiModelProperty
#NotBlank
#JsonView(BankAccountView.Public.class)
private String bankName;
#ApiModelProperty
#JsonView(BankAccountView.Public.class)
private BankAccountType accountType;
#ApiModelProperty
#JsonView(BankAccountView.Public.class)
private BankAccountStatus status;
#ApiModelProperty
private Instant verificationDate;
#ApiModelProperty
#JsonView(BankAccountView.Public.class)
private String mask;
}
// BankAccountController class
#ApiOperation(value = "Fetch a list of all bank accounts")
#JsonView({BankAccountView.Public.class})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Bank accounts successfully retrieved", response = BankAccountView.Public.class, responseContainer = "List"),
#ApiResponse(code = 400, message = "Validation failed", response = ApiHttpClientErrorException.class),
#ApiResponse(code = 403, message = "User is not an employee", response = ResourceForbiddenException.class),
#ApiResponse(code = 404, message = "User not found", response = NoSuchElementException.class),
#ApiResponse(code = 500, message = "Internal server error", response = ApiHttpServerErrorException.class)
})
#GetMapping
public List<BankAccount> getAllBankAccounts() {
return service.getAll();
}
Thanks a lot! :)

If you're using Jackson, you can use #JsonIgnore.
else set hidden true for individual properties
#ApiModelProperty(position = 1, required = true, hidden=true, notes = "used to display user name")

Related

How to write unit test for Spring Boot Controller if it required user name which was got from Security Context Holder?

I want to write a unit to allow users to change their personal Information. However, I don't know how to include the Security Context Holder mock into Unit Test. Especially, it is required to extract the user name which was used to find the User Information by query commands in User Repository. Thanks so much for your support.
Note:
I have successfully sent this edit Information API by using Postman before but it required you have login first and using Bearer JWT to edit user's information.
Below is my Unit Test:
#Test
public void whenSendRequestToModifyUserInformation_returnUserWithNewInformation () throws Exception {
String userName = "thanhnghi";
InformationRespondDTO informationRespondDTO = mock(InformationRespondDTO.class);
Information information = mock(Information.class);
ObjectMapper objectMapper = new ObjectMapper();
ModifyUserRequestDTO modifyUserRequestDTO =
ModifyUserRequestDTO.builder()
.dateOfBirth(new Date())
.firstName("Martin")
.lastName("Charlie")
.address("12 Washington District")
.phoneNumber("0794562342")
.email("martinCharlie#gmail.com").build();
;
when(informationService.update(modifyUserRequestDTO)).thenReturn(information);
when(informationMapper.toDTO(information)).thenReturn(informationRespondDTO);
mvc.perform(MockMvcRequestBuilders.put("/api/users/information")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(modifyUserRequestDTO))
)
.andExpect(status().isOk())
.andDo(print());
}
}
And this is my Service:
#Override
public Information update(ModifyUserRequestDTO modifyUserRequestDTO) {
String userName = userLocal.getLocalUserName();
Users users = this.userService.findByUserName(userName);
Information information = informationMapper.toExistedInformation(modifyUserRequestDTO, users.getInformation());
return this.informationRepository.save(information);
}
And this is my ModifyRequestDTO:
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class ModifyUserRequestDTO {
#NotNull(message = "date of birth is required")
private Date dateOfBirth;
#Pattern(regexp = "[A-Za-z]+", message = "First name cannot be number or special characters")
#NotNull(message = "First name cannot be null")
private String firstName;
#Pattern(regexp = "[A-Za-z]+", message = "Last name cannot be number or special characters")
#NotNull(message = "Last name is required")
#NotEmpty(message = "Last name must not be empty")
private String lastName;
#NotNull(message = "Address is required")
#NotEmpty(message = "Address must not be empty")
private String address;
#Size(min = 10, max = 11, message = "Phone number must has at least 11 characters and no more")
#NotNull(message = "phone number is required")
#NotEmpty(message = "phone number must not be empty")
private String phoneNumber;
// #Pattern(regexp = "[A-Za-z0-9]+#[a-zA-Z0-9]{6}", message = "Invalid Email Address")
#Email(message = "Invalid Email Address")
#NotNull(message = "email is required")
#NotEmpty(message = "email must not be empty")
private String email;
}
Component to handle Security Context Holder to look for userName:
#Component
public class UserLocal {
public String getLocalUserName(){
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
if(userName == null){
throw new ResourceNotFoundException("You haven't Login !!!");
}
return userName;
}
}
And finally UserController:
#RestController
#RequestMapping("/api/users")
#CrossOrigin(maxAge = 3600, origins = "*")
public class UserController {
UserService userService;
InformationService informationService;
InformationMapper informationMapper;
#Autowired
public UserController(UserService userService, InformationService informationService, InformationMapper informationMapper) {
this.userService = userService;
this.informationService = informationService;
this.informationMapper = informationMapper;
}
#PutMapping ("/information" )
public InformationRespondDTO modifyInformation(#RequestBody #Valid ModifyUserRequestDTO modifyUserRequestDTO){
Information information = this.informationService.update(modifyUserRequestDTO);
return informationMapper.toDTO(information);
}
}
And this is my error log:
java.lang.AssertionError: Status expected:<200> but was:<400>
Expected :200
Actual :400
Several ways to do it such as using UserRequestPostProcessor or #WithMockUser / #WithUserDetails or even a customised #WithXXXXUser. Refer to the docs for more details.
For example, using UserRequestPostProcessor as follows should solve your problem:
mvc.perform(MockMvcRequestBuilders
.put("/api/users/information")
.with(user("someUserName"))

How to add an example in #ApiResponse with Swagger?

I would like to add an example with Swagger in my method, I have tried a few things, but they didn't work.
I have my Interface, where I define the method:
#Api(value = "test API")
#RequestMapping("/api/v1/product")
public interface TestController {
#ApiOperation(
value = "Service that return a Product",
notes = "This service returns a Product by the ID",
nickname = "getProductById",
response = ProductResponse.class)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "The request has succeeded.", response = ProductResponse.class),
#ApiResponse(code = 500, message = "Internal server error.", response = ProductResponse.class) })
#GetMapping(
value = "/productById",
produces = { "application/json" }
)
ResponseEntity<ProductResponse> getProductById(#RequestParam(value = "productId", required = true) String productId);
The ProductResponse class is the following:
#Getter
#Setter
#AllArgsConstructor
public class ProductResponse {
private Product product;
private CustomException customException;
}
The Product class is the following:
#Getter
#Setter
#AllArgsConstructor
public class Product {
#JsonProperty("id")
private String id;
#JsonProperty("productName")
private String productName;
#JsonProperty("productDescription")
private String productDescription;
#JsonProperty("unitPrice")
private Double unitPrice;
And the CustomException class is the following:
#Getter
public class CustomException {
private final String message;
private final String errorCode;
private final String errorType;
private final Exception exceptionDetail;
public CustomException(String message, String errorCode, String errorType, Exception exceptionDetail) {
this.message = message;
this.errorCode = errorCode;
this.errorType = errorType;
this.exceptionDetail = exceptionDetail;
}
When the response is 200, the response is like:
{
"product": {
"id": "12345",
"productName": "Product name",
"productDescription": "This is a description",
"unitPrice": 3.25
},
"customException": null
}
But when the response is 500, the response is like:
{
"product": "null,",
"customException": {
"message": "/ by zero",
"errorCode": "500",
"errorType": "Internal server error",
"exceptionDetail": null,
"cause": null,
"stackTrace": [
{
"classLoaderName": "app",
"moduleName": null,
"moduleVersion": null,
"methodName": "getProductById",
"fileName": "TestControllerImpl.java",
"lineNumber": 33,
"className": "com.myproject.testmicroservice.controller.impl.TestControllerImpl",
"nativeMethod": false
}
]
}
}
How can I add a custom example in the #ApiResponse annotation?
You are probably missing the #Operation annotation, where inside you put the #ApiResponse.
Example:
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
#Operation(responses = {
#ApiResponse(responseCode = "200", content = #Content(examples = {
#ExampleObject(name = "getUserAttribute",
summary = "Retrieves a User's attributes.",
description = "Retrieves a User's attributes.",
value = "[{\"value\": [\"area1\", \"area2\", \"area3\"], \"key\":\"GENERAL_AREAS\"}, {\"value\":\"933933933\", \"key\":\"FONyE\"}]")
}, mediaType = MediaType.APPLICATION_JSON_VALUE))})
public ResponseEntity<List<UserPreferenceDto>> getUserPreferenceByCode(
#Pattern(regexp = "\\w+") #PathVariable String userCode, #Parameter(hidden = true) Pageable pageable) {
...
}
Good evening hope you are doing well. In the case you are describing, I would do something like this
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = "Found the book",
content = { #Content(mediaType = "application/json",
schema = #Schema(implementation = Book.class)) }),
#ApiResponse(responseCode = "400", description = "Invalid id supplied",
content = #Content),
the approach described is explained here. I think that paragraph 9. Generate Documentation Using #Operation and #ApiResponses is of particular interest in your case. I hope this helps, Have a good night
You can try something like this. In your controller you already have #ApiResponses annotation. What you need to do is add #ApiModel to your Product class and then add
#ApiModelProperty(notes = "Your comments", required = true, example = "example value")
to members of your Product class i.e. ProductResponse and CustomException. One thing that you will need to verify is whether #ApiModelProperty can be set on custom objects like ProductResponse and CustomException. If not you will need to set #ApiModelProperty 1 level deep.
As shown in article, the examples are auto populated from model property to response.
PS: As of now, I do not have setup of a swagger project so can only help you theoretically.
may be this late answer but incase any one need it, you can add the requestBody description along with content type within #Operation
#io.swagger.v3.oas.annotations.Operation(summary = "", description = "",
requestBody = #io.swagger.v3.oas.annotations.parameters.RequestBody(content = #Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))

Sprinboot backend and reactjs frontend FormData requst error

My springboot model is this:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Document(collection = "products")
public class Product {
#Id
private String id;
#NotEmpty(message = "name is mandatory")
private String name;
#NotEmpty(message = "price is mandatory")
private int price;
private MultipartFile file;
}
#AllArgsConstructor
#Data
#Builder
public class AddProductCommand implements Serializable {
#TargetAggregateIdentifier
private String id;
#NotNull(message = "no product details were supplied")
#Valid
private Product product;
public AddProductCommand(){
}
}
Since I have to send a MultipartFile from the reactjs, I must use FormData. I have tried the following:
async handleSubmit(event) {
event.preventDefault();
try {
let product = new FormData();
product.append("name", this.state.name);
product.append("price", this.state.price);
product.append("file", this.state.file);
let addProductCommand = new FormData();
addProductCommand.append("product", product);
const response = await axios.post(SELL_URL, addProductCommand , {
headers: {
'Content-Type': 'multipart/form-data'
}
});
this.clearState();
event.target.reset();
this.props.history.push("/buy");
} catch (err) {
console.log(JSON.stringify(err));
}
}
However, I got following error in the springboot:
DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'addProductCommand' on field 'product': rejected value [[object FormData]]; codes [typeMismatch.addProductCommand.product,typeMismatch.product,typeMismatch.com.cognizant.user.core.models.Product,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addProductCommand.product,product]; arguments []; default message [product]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'com.cognizant.user.core.models.Product' for property 'product'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.cognizant.user.core.models.Product' for property 'product': no matching editors or conversion strategy found]]
What have I missed and how should I fix this error?
Add controller code. I am trying to implement it as CQRS design. Here is the controller. I nedd to write more description not much code. Otherwise stackoverflow doesn't allow me do add more code :(.
#RestController
#RequestMapping(path = "/api/v1/addProduct")
public class AddProductController {
private final CommandGateway commandGateway;
#Autowired
public AddProductController(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
#PostMapping
public ResponseEntity<AddProductResponse> registerUser(#Valid #ModelAttribute AddProductCommand command) {
var id = UUID.randomUUID().toString();
command.setId(id);
try {
commandGateway.sendAndWait(command);
return new ResponseEntity<>(new AddProductResponse(id, "Product added successfully!"), HttpStatus.CREATED);
} catch (Exception e) {
var safeErrorMessage = "Error while processing add product request for id - " + id;
System.out.println(e);
return new ResponseEntity<>(new AddProductResponse(id, safeErrorMessage), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
for the above scenario, you are appending form data to the product key addProductCommand.append("product", product);
so in your controller, you should get the product entity. add this to your controller; #ModelAttribute("product")
public ResponseEntity<AddProductResponse> registerUser(#Valid #ModelAttribute("product") AddProductCommand command)

Need to Reject POST/PUT request if any unknown field is present in RequestBody

#Valid check is working for respective fields. Is there any way to reject requests if any unknown fields are present in JSON requestbody of POST/PUT requests.Below is my sample DTO class and controller.
For below sample request body (for example), the request should be rejected/throw exception.
Any help or suggestion would be appreciated.
{
"accountid" : "P12345",
"name" : "Cardiology",
"domain" : "Apollo"
}
public class Account {
#NotEmpty(message = "accountid is required")
private String accountid;
#NotEmpty(message = "name is required")
private String name;
//getters & setters
}
**********************************************************************************************
public class BeanController {
#PostMapping(path = "/accounts")
public ResponseEntity<?> getAllAccounts(#RequestBody #Valid Account account) {
System.out.println("::: Account is " + account + " :::");
return ResponseEntity.ok().body("SUCCESS");
}
}
You can do it by using #JsonIgnoreProperties.
#JsonIgnoreProperties(ignoreUnknown = false)
public class Account {
#NotEmpty(message = "accountid is required")
private String accountid;
#NotEmpty(message = "name is required")
private String name;
//getters & setters
}
Add below properties in application.yml to working in spring-boot latest version.
spring:
jackson:
deserialization:
fail-on-unknown-properties: true

Validate Request body in spring using #Valid

I am want to validate a JSON object, for length of an attribute. I am using #Size annotation to specify maximum length as shown below.
#JsonRootName("question")
public class QuestionJson {
#JsonProperty(value = "id", required = false)
private Long id;
#JsonProperty(value = "text", required = true)
private String label;
#JsonProperty(value = "answers", required = true)
private List<AnswerJson> answers;
}
#JsonRootName("answer")
public class AnswerJson {
#JsonProperty(value = "id", required = false)
private Long id;
#JsonProperty(value = "type", required = true)
private String type;
#JsonProperty(value = "label", required = true)
#Size(message = "size should not be long", max = 10)
private String label;
}
My request mapping in controller looks like:
#RequestMapping(value = "/api/answer", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public AnswerJson createQuestion(#RequestHeader(HttpHeaders.AUTHORIZATION) final String authorizationHeader, #Valid #RequestBody final QuestionJson questionJson) {
// some code here....
return answer;
}
UPDATE: Validation works on the outer elements eg. text in my case but fails on the nested list.
Every time we use #Valid annotation we also include a BindingResult instance as a method parameter, it contains the #Valid marked parameter errors if any:
public final #ResponseBody String theMethod(
final #Valid ValidableObjectImpl validableObject,
BindingResult result) {
try {
if (result.hasErrors()) {
for (FieldError error : result.getFieldErrors()){
// do something
}
// return error
}
} catch (Exception e) {
// ...
}
}
Found the solution. We need add #Valid annotation to the before the declaration of the nested object. eg
#JsonRootName("question")
public class QuestionJson {
#JsonProperty(value = "id", required = false)
private Long id;
#JsonProperty(value = "text", required = true)
private String label;
#JsonProperty(value = "answers", required = true)
#Valid
private List<AnswerJson> answers;
}

Categories

Resources