I am using GSON to serialise and deserialise JSON response while supplying as payload and mapping response to the data model.
Now, here id is auto-incremented from DB so we don't need to pass when creating payload.
JSON payload: (updateCustomer)
{"first_name":"test", "last_name":"user"}
public class Address {
#SerializedName("id")
private Integer id;
#SerializedName("first_name")
private String firstname;
#SerializedName("last_name")
private String lastname;
....
}
Test:
Response response =
given()
.filter(new RequestLoggingFilter(this.requestCapture))
.filter(new ResponseLoggingFilter(this.responseCapture))
.filter(new ErrorLoggingFilter(this.errorCapture))
.header("Authorization", getSession().getToken())
.body(updateCustomer)
.when()
.put(Resource.UPDATE_CUSTOMER)
.then()
.extract().response();
Expected Response in response instance
{"id":2234545, "first_name":"test", "last_name":"user"}
Response.toString() returns io.restassured.internal.RestAssuredResponseImpl#46320c9a instead of Response body string.
I've tried response.body().toString(),
#Expose(deserialize = false)
#SerializedName("id")
private Integer id;
but no luck.
Expecting the response body as the string so that I can map using GSON to Java objects to perform the validation but getting io.restassured.internal.RestAssuredResponseImpl#46320c9a
I appreciate if someone can please direct me on this issue.
Many thanks,
#Dipesh
Instead of response.body().toString(); try response.getBody().asString();
See the sample code I have done below and the output
Code
package com.restassured.framework.sample;
import static io.restassured.RestAssured.given;
import org.testng.annotations.Test;
import io.restassured.response.Response;
/**
* #author vamsiravi
*
*/
public class RestAssuredExample {
#Test
public void sampleTest(){
Response response = given().baseUri("https://jsonplaceholder.typicode.com/").and().basePath("/posts/1").when().get().thenReturn();
System.out.println(response.body());
System.out.println("---------------------------");
System.out.println(response.getBody().asString());
}
}
Output
io.restassured.internal.RestAssuredResponseImpl#652a7737
---------------------------
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
given()
.filter(new RequestLoggingFilter(this.requestCapture))
.filter(new ResponseLoggingFilter(this.responseCapture))
.filter(new ErrorLoggingFilter(this.errorCapture))
.header("Authorization", getSession().getToken())
.body(updateCustomer)
.when()
.put(Resource.UPDATE_CUSTOMER)
.then()
.body("id", equalTo("2234545"));
Hamcrest matcher import:
import static org.hamcrest.core.IsEqual.equalTo;
Related
I am using org.springframework.boot:spring-boot-starter-validation:2.7.0(which in turn uses hibernate validator) to validate user input to rest controller.
I am using Spring Boot Web Starter (2.7.0) based project with #RestController annotation
My #GetMapping method is something like below -
#GetMapping(path = "/abcservice")
public Object abcService(
#RequestParam(value = "accountId", required = true) String accountId,
#Valid #RequestParam(value = "offset", required = false, defaultValue = "0") int offset,
#Valid #RequestParam(value = "limit", required = false, defaultValue = "10000") int limit
) throws Exception {
My problem is - I want the user to know about any input validation errors so they can correct and retry. But the framework is just giving 400 status code with below message.
{
"timestamp": "2022-08-03T16:10:14.554+00:00",
"status": 400,
"error": "Bad Request",
"path": "/somepath/abcservice"
}
On the server side the request is logged in warn.
2022-08-03 21:40:14.535 WARN 98866 --- [nio-8080-exec-1]
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.web.method.annotation.MethodArgumentTypeMismatchException:
Failed to convert value of type 'java.lang.String' to required type
'int'; nested exception is java.lang.NumberFormatException: For input
string: "0s"]
I want this above error message --> Failed to convert value of type 'java.lang.String' to required type 'int'; nested exception is java.lang.NumberFormatException: For input string: "0s" also to be passed on to user. Is there a easy configuration based way to achieve.
I think i can add a ControllerAdvice to handle this exception and include this message in the response from handler method. But this will be a couple of lines of code. Is there an even simpler way than the ControllerAdvice approach.
Similarly if the client don't pass the mandatory accountId param, the client is just getting the same 400 response as above. No details or hints to the client about what wrong they are doing or how they can fix it.. but on the server side i can see below warn log.
2022-08-03 21:59:20.195 WARN 235 --- [nio-8080-exec-3]
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.web.bind.MissingServletRequestParameterException:
Required request parameter 'accountId' for method parameter type
String is not present]
I want the client to know about this error/exception. Nothing secret here to hide (atleast in my case).
Edit - found this config -
server.error.include-message=always
Now the issue is, bad requests are sent with 500 status code, I want them to sent as 400. Then this Q is solved.
Validations made by #Valid return with 500 Status Code. Is there anyway to tell the server to return 400 response when validations fail (without using ControllerAdvice).
If you wish to test-- you can try -->
Annotate controller with #Validated.
And execute below method and you will see 500 error but would want this to be 400.
#GetMapping("/test")
public void test(#Valid #RequestParam(value = "studentId", required = false)
#Min(value=0, message="Can not be less than 0") #Max(value=200, message="Can not be above 200") Long studentId ) {
System.out.println("hit: ");
}
And hit - http://localhost:9099/test?studentId=400
The spring in-built solution without global exception handler and with minimal config is by adding the below property in the application.properties.
server.error.include-binding-errors=always
The above property can have three values:
always ----> All api's in the app will always return well defined validation error message response.
on-param ----> All api's in the app will conditionally return well defined validation error message response based on input request param field "errors"
never ---> to disable this feature.
Example Github Project Reference
Demo Test:
package com.example.demo;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
#RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#PostMapping("/test")
public void test(#Valid #RequestBody Student student) {
System.out.println("studentData: " + student);
}
}
class Student {
#NotNull(message = "firstName cannot be null")
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Override
public String toString() {
return "Student [firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
Request:
{
"firstName": null,
"lastName" : "sai"
}
Response: (with HTTP response code = 400)
{
"timestamp": "2022-08-04T05:23:58.837+00:00",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotNull.student.firstName",
"NotNull.firstName",
"NotNull.java.lang.String",
"NotNull"
],
"arguments": [
{
"codes": [
"student.firstName",
"firstName"
],
"arguments": null,
"defaultMessage": "firstName",
"code": "firstName"
}
],
"defaultMessage": "firstName cannot be null",
"objectName": "student",
"field": "firstName",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
}
],
"path": "/test"
}
Use #expection handler and controller advice this help to handle your issue
This appears to be a common error, but there also appears to be various solutions.
I have tried a number of them without any luck. The Jackson dependency was added, the MappingJackson2HttpMessageConverter object was used. Verified the MediaType.APPLICATION_JSON is being set.
Converting the POJO to a json string first and changing the Controller to accept a String instead of the specific object it works.
The following is a code snippet showing the controller which accepts a POJO and it generates the exception. There were some slight changes and hopefully no syntax errors.
The Controller
#RestController
#RequestMapping(path = "/main")
public class TestController
{
#GetMapping(path="/firstTest", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = "application/json")
public String login(#RequestBody MyObject1 obj1)
{
System.out.println("============Inside routine=============");
return "Returning....";
}
}
The Cosumer
private static void runTest()
{
MyObject1 obj1 = new MyObject(101, "Test");
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("key", "value");
HttpEntity<MyObject1 > packetEntity = new HttpEntity<>(obj1,headers);
ResponseEntity<String>result = restTemplate.exchange("http://localhost:8080/main/FirstTest", HttpMethod.GET, packetEntity, String.class);
System.out.println(result);
}
The Exception
Exception in thread "main" org.springframework.web.client.RestClientException: No HttpMessageConverter for com.demo.MyObject1 and content type "application/json"
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:964)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:740)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:677)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:586)
at com.demo.clientSide.SimplestGetExample.attemptLogin(Test.java:106)
at com.demo.clientSide.SimplestGetExample.main(Test.java:56)
POJO
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
public class MyObject1
{
#Setter #Getter int x;
#Setter #Getter String y;
public MyObject1() {}
public MyObject1(int x, String y)
{
this.x = x;
this.y = y;
}
}
Is there anything obvious that was missed with this example?
Since not completely certain the #RequestBody is required within the Controller.
Should I be able to pass in a POJO and have Spring do the conversion to json behind the scenes or it is expected that I explicitly do the conversion to json?
Updated Info
If the following line is commented out a slightly different Exception is received.
headers.setContentType(MediaType.APPLICATION_JSON);
One thread mentioned since this is a GET routine the ContentType shouldn't be set. The exception is basically the same but without the last part of the string "....and content type "application/json"
I have an api in spring, returning a bytearray. In case any error happens, is it logical/possible to add a response body ?
eg:
#GetMapping(value = ["path"], produces = ["application/pdf"])
#ResponseBody
fun method(#PathVariable("varib") var: String?,
request: HttpServletRequest?, response:
HttpServletResponse?): ByteArray? {
// some method which will return a byte arrat
return pdf
}
#ExceptionHandler(RUnTimeException::class)
fun errorHandler(e: Exception,response: ServletRespons) {
response?.setHeader('someheader','value')
}
Is it possible to add a response body in case of exception ? from inside 'errorHandler' method ?is it logical ?
Depending on the version of Spring you are using, you don't need a #ResponseBody. You should rather use a #ControllerAdvice that help manage all your exceptions in your differents layers (repository, service, controller). You can define a custom response message that will represent your #Responsebody. I hope this link can help you.
You could add ControllerAdvice:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
#Slf4j
public class ExceptionHandlingAdvice {
private final Clock clock = Clock.systemDefaultZone();
#ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
return handleException(ex.getLocalizedMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
private ResponseEntity<ErrorResponse> handleException(String message, HttpStatus httpStatus) {
log.error("Error: " + message);
ErrorResponse errorResponse = ErrorResponse.of(Instant.now(clock), message);
return new ResponseEntity<>(errorResponse, httpStatus);
}
}
I've been working with Spring Boot 2.0.0.RC1 and use spring-boot-starter-webflux in order to build a REST Controller that returns a flux of text data.
#GetMapping(value = "/")
public Flux<String> getData(){
return Flux.interval(Duration.ofSeconds(2))
.map(l -> "Some text with umlauts (e.g. ä, ö, ü)...");
}
Since the text data contains some umlauts (e.g. ä, ö, ü), I would like to change the Content-Type header of the response from text/event-stream to text/event-stream;charset=UTF-8. Therefore, I tried wrapping to flux into a ResponseEntity. Like this:
#GetMapping(value = "/")
public ResponseEntity<Flux<String>> getData(){
return ResponseEntity
.ok()
.contentType(MediaType.parseMediaType("text/event-stream;charset=UTF-8"))
.body(Flux.interval(Duration.ofSeconds(2))
.map(l -> "Some text with umlauts (e.g. ä, ö, ü)..."));
}
Now, making a curl request to the endpoint shows that the Content-Type remains the same:
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: text/event-stream
<
data:Some text with umlauts (e.g. ├ñ, ├Â, ├╝)...
I suspected the MediaType.parseMediaType() method to be the issue, but the media type is parsed correctly (as this screenshot shows):
However, the parameter charset seems to be ignored. How can I change the encoding to UTF-8 so that the browser interprets the umlaut characters correctly?
EDIT: Setting within the GetMapping annotation the produces field does not work either.
#GetMapping(value = "/", produces = "text/event-stream;charset=UTF-8")
public ResponseEntity<Flux<String>> getData(){
return ResponseEntity
.accepted()
.contentType(MediaType.parseMediaType("text/event-stream;charset=UTF-8"))
.body(Flux.interval(Duration.ofSeconds(2))
.map(l -> "Some text with umlauts (e.g. ä, ö, ü)..."));
}
You can create a Filter and process response before this return to browser
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.core.Ordered;
// esse filtro foi criado pra converter para UTF-8 o response do Flux<ServerSentEvent<String>>
// this filter was created to convert all responses to UTF8, including Flux<ServerSentEvent<String>>
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class FluxPreProcessorFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
}
I have a method which queries a remote service. This service returns a single payload which holds many items.
How do I get those items out using a Flux and a flatMapMany?
At the moment my "fetch from service" method looks like:
public Flux<Stack> listAll() {
return this.webClient
.get()
.uri("/projects")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMapMany(response -> response.bodyToFlux(Stack.class));
}
a Stack is just a POJO which looks like:
public class Stack {
String id;
String name;
String title;
String created;
}
Nothing special here, but I think my deserializer is wrong:
protected Stack deserializeObject(JsonParser jsonParser, DeserializationContext deserializationContext, ObjectCodec objectCodec, JsonNode jsonNode) throws IOException {
log.info("JsonNode {}", jsonNode);
return Stack.builder()
.id(nullSafeValue(jsonNode.findValue("id"), String.class))
.name(nullSafeValue(jsonNode.findValue("name"), String.class))
.title(nullSafeValue(jsonNode.findValue("title"), String.class))
.created(nullSafeValue(jsonNode.findValue("created"), String.class))
.build();
}
What I've noticed happening is the first object is serialized correctly, but then it seems to get serialized again, rather than the next object in the payload.
The payload coming in follows standard JSON API spec and looks like:
{
"data":[
{
"type":"stacks",
"id":"1",
"attributes":{
"name":"name_1",
"title":"title_1",
"created":"2017-03-31 12:27:59",
"created_unix":1490916479
}
},
{
"type":"stacks",
"id":"2",
"attributes":{
"name":"name_2",
"title":"title_2",
"created":"2017-03-31 12:28:00",
"created_unix":1490916480
}
},
{
"type":"stacks",
"id":"3",
"attributes":{
"name":"name_3",
"title":"title_3",
"created":"2017-03-31 12:28:01",
"created_unix":1490916481
}
}
]
}
I've based this pattern on the spring-reactive-university
Any help as to where I've gone wrong would be awesome;
Cheers!
I think I solved it, still using a Flux.
public Flux<Stack> listAllStacks() {
return this.webClient
.get()
.uri("/naut/projects")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.toEntity(String.class))
.flatMapMany(this::transformPayloadToStack);
}
Converts the incoming payload to a String where I can then parse it using a jsonapi library
private Flux<Stack> transformPayloadToStack(ResponseEntity<String> payload) {
ObjectMapper objectMapper = new ObjectMapper();
ResourceConverter resourceConverter = new ResourceConverter(objectMapper, Stack.class);
List<Stack> stackList = resourceConverter.readDocumentCollection(payload.getBody().getBytes(), Stack.class).get();
return Flux.fromIterable(stackList);
}
Which returns a Flux. Thanks to the library, I don't need to create a bunch of domains either, I can still work with my simple Stack POJO
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#Type("stacks")
public class Stack {
#com.github.jasminb.jsonapi.annotations.Id
String id;
String name;
String title;
String created;
}
And this in turn is called from the controller
#GetMapping("/stacks")
#ResponseBody
public Flux<Stack> findAll() {
return this.stackService.listAllStacks();
}
I've not tested if this is blocking or not yet, but seems to work okay.
You json doesn't exactly match with your model class i.e. Stack. Together with Stack create another class like this
public class Data {
List<Stack> data;
// Getters and Setters....
}
Now in your webclient you can do like this
Mono<Data> listMono = webClient
.get()
.uri("/product/projects")
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Data.class));
Now if you do listMono.block() you will get Data object which will have all Stack objects.