HttpMediaTypeNotAcceptableException when application/json-patch+json is used as 'Content-Type' header - spring

we are using patch operation to support partial update.
#ApiOperation(value="Patch (Partial Update) user payment")
#RequestMapping(method = RequestMethod.PATCH, consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<UserPaymentInfo> patchPaymentInfo(#ApiParam(value = "The user id", required = true)#PathVariable final String uid,
#ApiParam(value = "Whether to reteurn new payment info back") #RequestParam(name = "includeResponse", defaultValue = "false") final boolean includeResponse,#ApiParam(value = "Description of changes")#RequestBody final String userPaymentInfoPatchJson) {
UserPaymentInfo paymentInfo = userPaymentService.patchPaymentInfo(uid,userPaymentInfoPatchJson,includeResponse);
HttpStatus status = includeResponse ? HttpStatus.OK : HttpStatus.NO_CONTENT;
return new ResponseEntity<>(paymentInfo,status);
}
Below mentioned is exception :
{"code": "0000",
"message": "Could not find acceptable representation (HttpMediaTypeNotAcceptableException)",
"host": "localhost",
"url": "/users/000020800464/paymentinfo/test",
"method": "PATCH",
"causes": [
{
"code": "0000",
"message": "Could not find acceptable representation (HttpMediaTypeNotAcceptableException)"
}
]
}
The same endpoint works good for application/json as header value of Content-Type but 'application/json-patch+json' fails.
Does spring boot support patch bcs i am not able to find related header name mentioned in org.springframework.http.MediaType.java

Related

Does having a custom error response mean that you have to catch any exception in order to be consistent?

In my spring boot project i have a User class and its' fields have annotation constrains like #Size, #Pattern #NotNull etc.
For example
#Id
#Column(name = "name", nullable = false, length = 16, unique = true)
#NotNull
#Size(max = 16, message = "Username should be less or equal than 16 characters")
#Pattern(regexp = "[^\s]*", message = "Username should not contain whitespaces")
#Pattern(regexp = "^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$", message = "Username should should start with a letter")
private String userName;
A post request with invalid userName returns the following error response
{
"timestamp":"2021-06-28T18:02:02.720+00:00",
"status":400,
"error":"Bad Request",
"message":"Validation failed for object='user'. Error count: 1",
"errors":[
{
"codes":[
"Pattern.user.userName",
"Pattern.userName",
"Pattern.java.lang.String",
"Pattern"
],
"arguments":[
{
"codes":[
"user.userName",
"userName"
],
"arguments":null,
"defaultMessage":"userName",
"code":"userName"
},
[
],
{
"defaultMessage":"^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$",
"arguments":null,
"codes":[
"^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$"
]
}
],
"defaultMessage":"Username should should start with a letter",
"objectName":"user",
"field":"userName",
"rejectedValue":"5",
"bindingFailure":false,
"code":"Pattern"
}
],
"path":"/signup"
}
Before questioning if this kind of error format is what i need, i didn't like it so i tried to make my own like in this guide Baeldung
I have a global controller now to deal with custom errors like when the username is taken.
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {...}
This is what i get now
{
"timestamp": "29-06-2021 05:05:03",
"status": "BAD_REQUEST",
"message": "Invalid input",
"path": "/signup",
"errors": [
{
"field": "userName",
"message": "Username should should start with a letter",
"rejectedValue": "5"
}
]
}
I suppose a good API means that you have to be consistent, that is we always have to return an error response with the same structure.
I have override some ResponseEntityExceptionHandler's method in order to catch other errors but there are still many methods to override. Here is a list of the remaining methods.
// handleBindException
// handleTypeMismatch
// handleMissingServletRequestPart
// handleMissingServletRequestParameter
// handleMethodArgumentTypeMismatch
// handleConstraintViolation
// handleHttpMediaTypeNotAcceptable
// handleMissingPathVariable
// handleServletRequestBindingException
// handleConversionNotSupported
// handleHttpMessageNotWritable
// handleAsyncRequestTimeoutException
My questions:
Do i have to catch all these exceptions? To be more specific, is it always possible to take all these exceptions no matter how your domains, controllers, services work?
Can you please write for each of these exceptions a bad request that will cause them to be thrown? Please, don't just tell me when they will be thrown. I'm new to spring and i won't be able to understand without an example.
How about overriding the below method from ResponseEntityExceptionHandler, as it is being invoked by all methods mentioned in the query.
protected ResponseEntity<Object> handleExceptionInternal()
And have your own logic to check the instance of exception and provide different kind of error response to client.

How to create multiple schema in #RequestBody of swagger openapi specification 3.0 using springdoc?

I have the below api for which I need to have two parameters of content type application/x-www-form-urlencoded and therefore am using #RequestBody instead of #Parameter
#Operation(summary = "Revoke given permissions", description = "Allows admin to revoke permissions to users")
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void revokePermission(
#RequestBody(description = "the permission id", content = #Content(mediaType = "application/x-www-form-urlencoded",
schema = { #Schema(type = "String", name = "permission_id",
description = "id of the permission to be revoked", required = true)},
{ #Schema(type = "String", name = "permission_type",
description = "the permission type")}))
String permission_id, String permissionType) {
do_something();
}
I need the swagger.json to be like below example, but I do not know how to generate it using springdoc .I tried #ArraySchema also , but I am not getting the output I need. I am making some mistakes in the syntax and not able to find examples online.
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"properties": {
"permission_id": {
"description": "id of the permission to be revoked",
"type": "string"
},
"permission_type": {
"description": "the permission type",
"type": "string"
}
},
"required": ["permission_id"]
}
}
}
}
Any help is highly appreciated. TIA
The The simplest way to achieve what you want is to define the permission data in simple object as follow:
#Schema(name = "permissionData")
public class PermissionData {
#Schema(type = "String", name = "permiddionId", description = "id of the permission to be revoked", required = true)
#JsonProperty("permiddionId")
String permiddionId;
#Schema(type = "String", name = "permissionType",description = "the permission type")
#JsonProperty("permissionType")
String permissionType;
}
And then you controller method:
#Operation(summary = "Revoke given permissions", description = "Allows admin to revoke permissions to users")
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void revokePermission(#RequestBody(description = "the permission data") PermissionData permissionData) {
}

How to remove unwanted keys from rest-assured response object and assert remaining object data with constant variable having json string using java

In rest-assured test cases I am getting response as mentioned, where I want to remove keys such as "updated_at", "deleted_at", "created_at" and "notice" and then assert this response object with expected json string constant which contains 'settings'
{
"notice": "The Settings are updated successfully.",
"settings": {
"push_notification": {
"enabled": true,
"credentials": [{
"key": "value"
}],
"service_name": "API Testing"
},
"created_at": "2019-05-04T14:52:32.773Z",
"deleted_at": "false",
"updated_at": "2019-05-07T11:23:22.781Z"
}
}
For given response the expected json string is...
public static String SETTING_EXPECTED = "{\"push_notification\": {\"enabled\": true, \"credentials\": [{\"key\": \"value\"}], \"service_name\": \"API Testing\"}}"
Please help me with creating a common method using java which can be reuse for response assertions in all the test cases.
To delete keys from response you can use below code I am using jayway jsonpath library, you need to pass Json Response and field name jsonPath, in case your it will be "$.settings.created_at" :
public String deleteFieldNameFromResponse(String jsonResponse, String fieldToDelete)
throws ParseException, FileNotFoundException, IOException {
Object obj = null;
JSONParser parser = new JSONParser();
JsonPath jsonPath = null;
DocumentContext docCtx = null;
obj = parser.parse(jsonResponse);
docCtx = JsonPath.parse(obj);
docCtx.delete(fieldToDelete);
jsonPath = JsonPath.compile("$");
return docCtx.read(jsonPath).toString();
}

Swagger UI not rendering the correct response(Should return a JSON response)[This is for the GET request Code 200]

Swagger UI is not returning the expected JSON response in the example value. It is returning a empty list.Below are the code snippets I am using,
Gradle Dependency
compile('io.springfox:springfox-swagger-ui:2.9.2')
compile('io.springfox:springfox-swagger2:2.9.2')
Swagger Config
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.ignoredParameterTypes(HttpServletResponse.class)
.select() .apis(RequestHandlerSelectors.basePackage("com.core.controller.v2"))
.paths(PathSelectors.any())
.build()
.enable(true)
.apiInfo(apiInfo())
.securityContexts(Lists.newArrayList(securityContext()))
.securitySchemes(Lists.newArrayList(apiKey()));
Controller
#ApiOperation(value="A GET request to get a list of all contents for a given user.",
notes = "This API is used to get the contents for a given user with an NPI and Partner ID",
response = CoreContentItem.class)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Success response", response = CoreContentItem.class,responseContainer = "List"),
#ApiResponse(code = 401, message = "Unauthorized"),
#ApiResponse(code = 400, message = "Bad Request",responseContainer = "String"),
#ApiResponse(code = 403, message = "Forbidden"),
#ApiResponse(code = 500, message = "Internal Server Error, please contact system administrator")})
Swagger Output
Swagger UI for success Response
Expected JSON response
This is a sample expected JSON response
[
{
"item": {
"id": "3f94ea1a687dda4af3e2",
"category": null,
"type": "EXTERNAL",
"headline": "DO NOT DELETE - REST ASSURED All - HIGH - JV",
"summary": "DO NOT DELETE - All - HIGH - JV",
"details": null,
"mediaURL": "",
"createdOn": 1493658088000,
"modifiedOn": 1495553312000,
"priority": "HIGH",
"startDate": 1493618400000,
"endDate": 1588312800000,
"feedbackEmail": null,
"totalLikes": 0,
"totalViews": 2,
"customData": null,
"userInteraction": {
"userLiked": false,
"userDisliked": false,
"userViewed": false
},
"availableActions": [
"View",
"Done",
"Submit"
],
"externalURL": "https://www.1234.com/vegetables/armando%25e2%2580%2599s-chiles-rellenos/r/5014"
}
}
]
I found the issue. Apparently it was a model mapping exception that made the Swagger UI to return an empty list. Now I am able to see the model in example value.
enter image description here

How to change content type for two different objectives in Spring Controller?

I am working on an API that displays JSON data and downloads CSV in one single API.
The problem is how to change Content-type of my header when I intend to download CSV file ?
Below is my code :
#RequestMapping(value = "${api.route.get.all.report}", method = RequestMethod.POST)
#PreAuthorize("hasAnyAuthority('super_admin','owner','admin')")
public ResponseEntity<?> getReportForAll(
#ApiParam("partnerId") #RequestParam(value = "partnerId", required = false) String partnerId,
#ApiParam("orgId") #RequestParam(value = "orgId", required = false) String orgId,
#ApiParam("eventId") #RequestParam(value = "eventId", required = false) String eventId,
#ApiParam("export") #RequestParam(value = "export") boolean export,
#ApiParam("Search Filter") #RequestBody SearchCriteriaDTO filterRequestDTO,
HttpServletResponse httpServletResponse) throws WazooException, IOException {
Object response = reportService.getReportsForAll(filterRequestDTO, partnerId, orgId, eventId, export,
httpServletResponse);
if (export) {
httpServletResponse.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE);
httpServletResponse.setHeader("Content-Disposition", "filename=" + response);
return ResponseEntity.ok(waasAppUtils.createResponseEntityDTO(HttpStatusCodes.OK,
applicationUtility.getMessage("fetched"), response));
} else {
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
return ResponseEntity.ok(waasAppUtils.createResponseEntityDTO(HttpStatusCodes.OK,
applicationUtility.getMessage("fetched"), response));
}
}
If the purpose is only to display data(when JSON data is populated), then its working as expected and I am getting following Headers :
Content-Type →application/json;charset=UTF-8
Date →Wed, 14 Mar 2018 12:27:07 GMT
Expires →0
Here is my response
{
"response_code": 200,
"response_message": null,
"response_body": [
{
"name": "",
"totalCharges": {
"platformCharge": 0.5,
"totalCharge": 0.2,
"basicCharge": 0.3
},
"id": "5a97a5930467kf42f6a2eof1"
},
All good till this point. Now the problem is, when I wish to download CSV(export flag set to true), it returns simply the file name in response body :
{
"response_code": 200,
"response_message": null,
"response_body": "/home/reports/Report_Wed Mar 14 12:26:56 UTC 2018.csv"
}
and the content-type is still displaying me "application/json;charset=UTF-8"
How can i change the content type when the flag is set to be true and display data accordingly ???
If you want to return just file in case of export, try this out:
...
if (export) {
return ResponseEntity
.ok()
.contentType(MediaType.parseMediaType("text/csv"))
.header("Content-Disposition", "filename=" + fileName)
.body(<put your file content here as byte array>);
}
...

Resources