We are creating a REST API for managing a resource. We are using Jersey framework.
Following is the structure of the JSON
cable {
"id": 1,
"name": "Cable 1",
"description": "Test Cable",
"Site": 4
}
The JSON should map to the below class.
public class CableDTO {
private Long id;
private String name;
private String description;
//private SiteDTO site;
private Long siteID;
.
.
.
// Getters ans Setters
}
Site is actually a reference object. The input and output JSON, requires the CableDTO to contain Site id instead of the entire Site object. We have assemblers which convert the DTO's to Domain objects and vice-versa.
The sample assembler code is as below:
public class CableAssembler {
//some code
public void fillDomain(Cable cable, CableDto dto){
if(dto.getId() != null) cable.setID(dto.getId());
if(dto.getSiteID() != null) cable.setSiteId(dto.getSiteId())
//some more code below
}
}
This assembler works well when a brand new POST HTTP method is called. If the SiteID is not set in the input json, the site is set to null and all is hunky dory. However, on a PUT HTTP call, if the SiteID is not set, the existing SiteID is over written to null. This is because the same assembler is used in both PUT and POST call. The assembler checks if the siteID set in the DTO is null. If it is null, the existing siteID is overwritten. The problem is, I am not able to distinguish between whether the null value in the SiteID is because the value was not sent or the SiteID was indeed deliberately set to null (which is possible {"siteID":null} )
Any pointers on how to solve this?
We use Jackson for serialization/de-serialization.
If I understood right your situation, my propositions are:
If you can, add a boolean field to JSON and DTO telling if the siteID was set and sent and check it in the assembler or other code;
Give the siteId another default value, different from null, like -1 or 0, if it's possible.
It seems that you use your PUT request like PATCH request. To use PUT request properly you should GET cable info, change some fields and PUT back. In this case your siteId field will remain unchanged.
Related
I am taking a JSON file as input for a class and parsing the values using gson through respective data classes.
I want to call a function that takes a String value as an argument.
The string value allowed is decided from the values parsed from JSON file. Can I somehow check for that string value passed to the function at compile-time & give an error at compile-time?
Or If I can allow only certain values in the argument for the function based on the values from JSON
Detailed Explanation of use case:
I am building a SDK in which a the person using sdk inputs json String. The json is standardised and is parsed in my code.
{
"name": "Test",
"objects": [
{
"name": "object1",
"type": "object1"
}
]
}
Here name values and other values may vary based on the input by the developer using it but key remains same. But we need to call a function using the value in objects name parameter.
fun testMethod(objectName:String)
So developer calls the testMethod as testMethod(object1).
I need to validate object1 parameter based on json but is there any way possible restricting the test method parameter to object1 only & give error at compile time if the developer calls testMethod(obj1)
Right now I parse JSON & have checks inside the testMethod()
Sure it's possible to do, but somehow in different way, that you described. First of all, as you already mentioned this behavior could be done easily. For this purpose we have Objects.requireNotNull() or Guava.Preconditions(). At the same way you can define you checking but this will work on runtime only.
To do in compile time, you need to create Annotation Preprocessor. The same, as did in different libraries, and one of them, could be Lombok, with their NotNull and Nullable. Android annotation just provide mark and bound for IDE warning, but in their case they adding NotNull checking and throw exception for every annotation usage during compile time.
It's not an easy way, but it's what you are looking for.
No, it's impossible check it in compiler time. It's string handling, as numeric calculation.
In my app, I convert string to JSON and JSON to string, passing class descriptor. My aim is record JSON string in a text file to load in SQLite database. This code I've run in my desktop computer not in Android.
data class calcDescr (
...
)
val calc = CalcDescr(...)
// toJson: internal Kotlin data to JSON
val content = Gson().toJson(calc)
//==================
// Testing validity
// ================
// fromJson: JSON to internal Kotlin data.
// It needs pass the class descriptor. Uses *Java* token, but it's *Kotlin*
var testModel = Gson().fromJson(content, CalcDescr::class.java)
// toJson: internal Kotlin data to JSON again
var contentAgain = Gson().toJson(testModel)
// shoul be equal!
if (content == contentAgain) println("***ok***")
In my file, I write the variable content in a file
I have a method which builds an object and returns it.
The object as UUID as one of its fields. While building the object, random UUID is generated. Here is the code:
public MetaData getMetaData(String id) {
return MetaData.newBuilder().setId(id)
.setCorrelationId(UUID.randomUUID().toString())
.setCreatedAt(now(UTC).format(ISO_ZONED_DATE_TIME))
.build();
}
Here is my test:
#Test
public void shouldVerifyIfTheMetaDataIsBuild() {
MetaData metaData = handler.getMetaData("1234");
assertThat(metaData.getId(), is("1234"));
assertThat(metaData.getCorrelationId(), isNotNull());
}
I'm just verifying if the correlationId is not null or not. Is there a better way to verify the UUID?
The only way to verify the current production code is to, yes, check that CorrelationId is not null. You could also check that the layout of the expected string matches a valid UUID string.
Of course, when you want to a bit of better checking, then you simply have to replace UUID.randomUUID().toString() with something that you can control.
For example you could create a UUIDGenerator interface, with a default method that creates a random UUID, as shown in your code. But in your test setup, you instead provide an implementation of that interface that returns a specific UUID.
Then your test code could know which UUID should be used, and assertThat(actualUuid, is(expectedUuid)).
I would change this line:
assertThat(metaData.getCorrelationId().toString(), isNotNull());
to the following:
assertThat(metaData.getCorrelationId(), isNotNull());
otherwise you would get a NullPointerException if metaData.getCorrelationId() returns null instead of an assertion failure.
Additionally, you could test if metaData.getCorrelationId() returns a string that represents a valid UUID, by trying to parse it:
try {
UUID.fromString(metaData.getCorrelationID());
} catch (IllegalArgumentException e) {
fail("Correlation ID is not a valid UUID: " + metaData.getCorrelationId());
}
When using a UUID, I check the String pattern using a regex.
assertTrue(id.matches(Pattern("([a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8})")))
This way I check if it is present and if the String is indeed a UUID as I had it happen that an unintended change put a space there which would pass in the assertNotNull solutions.
One more thing you can validate about UUID is it's length (as it is always 36 java.util.UUID.randomUUID().toString() length).
There will be a chance for detecting changes in implementation in future.
I added a UUID validator to Apache Commons Validator. It's not yet been merged, but you can vote for it here: https://github.com/apache/commons-validator/pull/68
As rightly said by #GhostCat, since we don't have control over UUID.randomUUID(), the only way would be to check if the UUID generated is not null. For that, you may use the code below:
assertNotNull(metaData.getCorrelationId());
We have some web services which are being consumed by mobile clients , in which the mobile clients make some request's and we return response to them. Somehow if the client make any invalid request we throw Custom Exceptions .But recently the mobile client has made some request which was out of range for Long variable. The client has different variables for ex ::
{
"accountId":"343"
"Amount":"90909090909090909090"
}
In case of the value for accountId or Amount are made more than 19 digits we get HttpMessageNotReadable exception as the range is out of long value. But from exception i am not able to fetch for which variable the exception has been raised whether for accountId or Amount. From the exception i am getting this information in _path variable but i am not able to fetch it.
And in the path variable i get something like::
[com.Upload["AccountInfo"], com.Info["Account"]]
Does somebody know how to fetch this information.
The following code prints out the field which causes the exception.
InvalidFormatException invalidFormatException = (InvalidFormatException) exception
.getCause();
System.out.println(invalidFormatException.getPath().get(0)
.getFieldName());
#ArunM's answer works as long as the field is in 1st level viz the example given by OP.
But what happens when the field is in nested json? say paymentType has wrong value in the following example?
{
"userType": "CUSTOMER",
"payment": {
"amount": 123456,
"paymentType": "INTERNET_BANKING"
}
}
In the above example, if there's anything wrong with the value of userType, there will be just one element in _path.
But if any value inside the payment is wrong, say for example paymentType, there will be multiple elements in the _path variable. Each element representing the hierarchical attribute.
So for paymentType, _path will have 2 elements as illustrated below:
_path[0].fieldName = "payment"
_path[1].fieldName = "paymentType"
Hence the correct approach is to get the last element in _path as shown below. If needed, we can build the complete path by using all the elements.
InvalidFormatException ifx = (InvalidFormatException) exception.getCause();
System.out.println(ifx.getPath().get(ifx.size() - 1).getFieldName());
This I believe is the right approach to get the invalid attribute name.
I started to use Spring REST Docs to document a simple REST API. I have a payload that has some hierarchical structure, for example like this (company with employees).
{
"companyName": "FooBar",
"employee":
[
{
"name": "Lorem",
"age": "42"
},
{
"name": "Ipsum",
"age": "24"
}
]
}
I would like to separate the documentation of the company object (name and array of employees) and the employee object (employee name and age).
Using the org.springframework.restdocs.payload.PayloadDocumentation.responseFields like explained here forces me to document all fields but in case I only want to document the employee fields - how can I achieve this?
I have no problem to document the company without the employee details because if a field is document the descendants are treated as been documented also. But I can not document the employee structure on its own and I have no dedicated payload for this structure without the company root object.
Inspired by this question, I've implemented an enhancement which makes the original answer (see below) obsolete.
If you use 1.0.0.BUILD-SNAPSHOT (available from https://repo.spring.io/libs-snapshot), you can now mark a field as ignored. Ignored fields count has having been documented without actually appearing in the documentation.
Given that you want to separate the documentation, having two document calls makes sense. In the first you can document the company name and the array of employees. In the second you document the employees array and mark the company name as ignored.
Your test would look something like this:
mockMvc.perform(get("/company/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("company",
responseFields(
fieldWithPath("companyName").description(
"The name of the company"),
fieldWithPath("employee").description(
"An array of the company's employees"))))
.andDo(document("employee",
responseFields(
fieldWithPath("companyName").ignored(),
fieldWithPath("employee[].name").description(
"The name of the employee"),
fieldWithPath("employee[].age").description(
"The age of the employee"))));
You'll end up with two directories of snippets, one named company and one named employee. You can then use the response-fields.adoc snippet from each.
Original answer
There's no explicit support for ignoring a field when you're documenting a request or a response, but I think you can probably achieve what you want by using a preprocessor to remove the fields that you don't want to document.
Given that you want to separate the documentation, having two document calls makes sense. In the first you can document the company name and the array of employees. In the second you need to preprocess the request to remove the company and then document the employees array.
Your test would look something like this:
mockMvc.perform(get("/company/5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(document("company",
responseFields(
fieldWithPath("companyName").description(
"The name of the company"),
fieldWithPath("employee").description(
"An array of the company's employees"))))
.andDo(document("employee",
preprocessResponse(removeCompany()),
responseFields(
fieldWithPath("employee[].name").description(
"The name of the employee"),
fieldWithPath("employee[].age").description(
"The age of the employee"))));
Note the use of preprocessResponse in the second document call. removeCompany returns a preprocessor that uses a custom ContentModifier to remove company name from the response:
private OperationPreprocessor removeCompany() {
return new ContentModifyingOperationPreprocessor(new ContentModifier() {
#Override
public byte[] modifyContent(byte[] originalContent, MediaType contentType) {
ObjectMapper objectMapper = new ObjectMapper();
try {
Map<?, ?> map = objectMapper.readValue(originalContent, Map.class);
map.remove("companyName");
return objectMapper.writeValueAsBytes(map);
}
catch (IOException ex) {
return originalContent;
}
}
});
}
You'll end up with two directories of snippets, one named company and one named employee. You can then use the response-fields.adoc snippet from each.
While the above will work, it's harder than it needs to be. I've opened an issue so that the preprocessing to modify the response's content will no longer be necessary.
This answer is for those who came here at the present time with the same question. Using version 2.0.x of Spring Rest Docs, you can do the following:
Document the first level with PayloadDocumentation.relaxedResponseFields methods (Any undocumented field will be ignored):
Document the second level with PayloadDocumentation.beneathPath that will extract the subsection of the JSON payload found beneath the given path
This will generate a response-fields.snippet file and a response-fields-beneath-employee.snippet file
.consumeWith(document("company",
PayloadDocumentation.relaxedResponseFields(
PayloadDocumentation.fieldWithPath("companyName").description("The name of the company"),
PayloadDocumentation.fieldWithPath("employee").description("An array of the company's employees")
),
//Using PayloadDocumentation.beneathPath(..) to document separately employee parts
PayloadDocumentation.responseFields(
PayloadDocumentation.beneathPath("employee"),
PayloadDocumentation.fieldWithPath("employee.name").description("The name of the employee"),
PayloadDocumentation.fieldWithPath("employee.age").description("The age of the employee")
)
)
i have thrift service with a function returning list of Object ABC:
struct ABC
{
1: string user_id;
2: string foo;
3: optional list<string> data;
}
list<ABC> getABCByUser(1:required string user_id, 2:i32 limit,3:i32 pageId, 4:string lastDocID)
throws (1:InvalidRequestException ire, 2:UnavailableException ue, 3:TimedOutException te)
server side written by c++
I print out result returned by server side, data in ABC instance is NOT null in the response of getABCByUser.
How ever on client side which is written by java:
I set break point in the code generated by thrift on java side, data in ABC instance is null, other fields are not null.
it looks like a issue on the client side. Any idea how to fix this issue?
thanks in advance!
I encounter the same problem with you. I found that if delete the "optional" modifier before list, the return value will be right. But I don't know why we can't use "optional" before list.
If you think you may have found a bug, please open a JIRA ticket and add your reproducible test case. This makes it easier for others to have a look at it. Thank you!