Skip to content

Commit 37d7bf6

Browse files
committed
Update version to 1.11.0
Add error meta field for meta object containing non-standard meta-information about the error
1 parent 6ec23a7 commit 37d7bf6

File tree

8 files changed

+186
-8
lines changed

8 files changed

+186
-8
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [1.11.0] - 2023-08-02
4+
### Added:
5+
- Error meta field for meta object containing non-standard meta-information about the error
6+
37
## [1.10.1] - 2023-06-17
48
### Fixed:
59
- Missing **@NoArgsConstructor** annotation on **Meta** class for proper jackson response deserialization

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,58 @@ public class Test {
506506
}
507507
```
508508
509+
Example response with error with meta field (data is empty so we use ```Void```class as generic parameter):
510+
```java
511+
// Pseudo code
512+
public class Test {
513+
public Response<Void> main() {
514+
final ErrorMetaDto errorMeta = new ErrorMetaDto(
515+
UUID.randomUUID(),
516+
"Test meta value",
517+
Set.of(
518+
"Value 1",
519+
"Value 2"
520+
)
521+
);
522+
return Response.<Void, Void>builder()
523+
.error(
524+
HttpStatus.BAD_REQUEST,
525+
"20045",
526+
"Some errors occurred!",
527+
errorMeta
528+
).build();
529+
}
530+
}
531+
```
532+
```json
533+
{
534+
"errors": [
535+
{
536+
"status": 400,
537+
"code": "20045",
538+
"detail": "Some errors occurred!",
539+
"meta": {
540+
"id": UUID,
541+
"value": "Test meta value",
542+
"codes": [
543+
"Value 1",
544+
"Value 2"
545+
]
546+
}
547+
}
548+
],
549+
"meta": {
550+
"api": {
551+
"version": "1"
552+
},
553+
"page": {
554+
"maxSize": 25,
555+
"total": 0
556+
}
557+
}
558+
}
559+
```
560+
509561
Example response with error with links (data is empty, so we use ```Void``` class as generic parameter):
510562
```java
511563
// Pseudo code

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>com.slm-dev</groupId>
66
<artifactId>jsonapi-simple</artifactId>
7-
<version>1.10.1</version>
7+
<version>1.11.0</version>
88
<name>jsonapi-simple</name>
99
<description>Simple implementation of the JSON:API specification</description>
1010
<url>https://slm-dev.com/jsonapi-simple/</url>

src/main/java/com/slmdev/jsonapi/simple/response/Error.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ public class Error {
2020
private String detail;
2121
@ApiModelProperty(value = "Parameter name only for validation errors")
2222
private Source source;
23+
@ApiModelProperty(value = "Links object can be used to represent links")
2324
private ErrorLink links;
25+
@ApiModelProperty(value = "Meta object containing non-standard meta-information about the error")
26+
private Object meta;
2427

2528
@Getter
2629
@ToString
@@ -29,6 +32,7 @@ public class Error {
2932
@Accessors(chain = true)
3033
@JsonInclude(JsonInclude.Include.NON_NULL)
3134
public static class Source {
35+
@ApiModelProperty(value = "Object containing references to the primary source of the error")
3236
private String parameter;
3337
}
3438

@@ -45,4 +49,14 @@ public static class ErrorLink {
4549
@ApiModelProperty(value = "A link that identifies the type of error that this particular error is an instance of")
4650
private String type;
4751
}
52+
@Getter
53+
@ToString
54+
@Builder
55+
@NoArgsConstructor
56+
@AllArgsConstructor
57+
@Accessors(chain = true)
58+
@JsonInclude(JsonInclude.Include.NON_NULL)
59+
public static class ErrorMeta {
60+
private Object meta;
61+
}
4862
}

src/main/java/com/slmdev/jsonapi/simple/response/Response.java

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ public ResponseBuilder<T, V> validationError(final @NonNull String errorValidati
347347
"VALIDATION_ERROR",
348348
detail,
349349
errorValidationField,
350+
null,
350351
null
351352
);
352353
}
@@ -359,7 +360,7 @@ public ResponseBuilder<T, V> validationError(final @NonNull String errorValidati
359360
* @return self link
360361
*/
361362
public ResponseBuilder<T, V> error(final HttpStatus status, final String detail) {
362-
return error(status, null, detail, null, null);
363+
return error(status, null, detail, null, null, null);
363364
}
364365

365366
/**
@@ -371,7 +372,7 @@ public ResponseBuilder<T, V> error(final HttpStatus status, final String detail)
371372
* @return self link
372373
*/
373374
public ResponseBuilder<T, V> error(final HttpStatus status, final String code, final String detail) {
374-
return error(status, code, detail, null, null);
375+
return error(status, code, detail, null, null, null);
375376
}
376377

377378
/**
@@ -390,7 +391,7 @@ public ResponseBuilder<T, V> error(final HttpStatus status,
390391
final String code,
391392
final @NonNull String detail,
392393
final String errorValidationField) {
393-
return error(status, code, detail, errorValidationField, null);
394+
return error(status, code, detail, errorValidationField, null, null);
394395
}
395396

396397
/**
@@ -409,7 +410,47 @@ public ResponseBuilder<T, V> error(final HttpStatus status,
409410
final String code,
410411
final @NonNull String detail,
411412
final Error.ErrorLink links) {
412-
return error(status, code, detail, null, links);
413+
return error(status, code, detail, null, links, null);
414+
}
415+
416+
/**
417+
* Create errors object.
418+
*
419+
* <p>We can invoke this method as many times as we need and
420+
* result object will contain all errors we passed.
421+
*
422+
* @param status spring {@link HttpStatus} object
423+
* @param code internal error code (if exists)
424+
* @param detail detail information about error
425+
* @param errorMeta field with meta info for the non-standard details about error
426+
* @return self link
427+
*/
428+
public ResponseBuilder<T, V> error(final HttpStatus status,
429+
final String code,
430+
final @NonNull String detail,
431+
final Error.ErrorMeta errorMeta) {
432+
return error(status, code, detail, null, null, errorMeta);
433+
}
434+
435+
/**
436+
* Create errors object.
437+
*
438+
* <p>We can invoke this method as many times as we need and
439+
* result object will contain all errors we passed.
440+
*
441+
* @param status spring {@link HttpStatus} object
442+
* @param code internal error code (if exists)
443+
* @param detail detail information about error
444+
* @param links field with links for the details about error
445+
* @param errorMeta field with meta info for the non-standard details about error
446+
* @return self link
447+
*/
448+
public ResponseBuilder<T, V> error(final HttpStatus status,
449+
final String code,
450+
final @NonNull String detail,
451+
final Error.ErrorLink links,
452+
final Error.ErrorMeta errorMeta) {
453+
return error(status, code, detail, null, links, errorMeta);
413454
}
414455

415456
/**
@@ -423,13 +464,15 @@ public ResponseBuilder<T, V> error(final HttpStatus status,
423464
* @param detail detail information about error
424465
* @param errorValidationField field with failed validation
425466
* @param links field with links for the details about error
467+
* @param errorMeta field with meta info for the non-standard details about error
426468
* @return self link
427469
*/
428470
public ResponseBuilder<T, V> error(final HttpStatus status,
429471
final String code,
430472
final @NonNull String detail,
431473
final String errorValidationField,
432-
final Error.ErrorLink links) {
474+
final Error.ErrorLink links,
475+
final Error.ErrorMeta errorMeta) {
433476
if (this.errors == null) {
434477
this.errors = new ArrayList<>();
435478
}
@@ -438,8 +481,10 @@ public ResponseBuilder<T, V> error(final HttpStatus status,
438481
if (StringUtils.hasText(errorValidationField)) {
439482
errorSource = new Error.Source(errorValidationField);
440483
}
484+
final Object errorMetaData = (errorMeta != null ? errorMeta.getMeta() : null);
485+
441486
this.errors.add(
442-
new Error(status.value(), code, detail, errorSource, links)
487+
new Error(status.value(), code, detail, errorSource, links, errorMetaData)
443488
);
444489
this.dataList = null;
445490
this.dataObject = null;

src/test/java/com/slmdev/jsonapi/simple/response/BaseTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import com.fasterxml.jackson.databind.json.JsonMapper;
55
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Data;
8+
import lombok.NoArgsConstructor;
69
import lombok.NonNull;
710
import org.springframework.http.HttpStatus;
811

912
import java.time.LocalDateTime;
1013
import java.time.ZoneOffset;
1114
import java.util.List;
1215
import java.util.Map;
16+
import java.util.Set;
1317
import java.util.UUID;
1418

1519
import static org.hamcrest.MatcherAssert.assertThat;
@@ -56,6 +60,14 @@ protected void assertResponseErrorCode(final @NonNull Response<?> response) {
5660
assertThat(response.getErrors().get(0).getCode(), is(ERROR_CODE));
5761
}
5862

63+
protected void assertResponseErrorMeta(final @NonNull Response<?> response, final @NonNull ErrorMetaDto requiredErrorMeta) {
64+
final ErrorMetaDto errorMeta = objectMapper.convertValue(response.getErrors().get(0).getMeta(), ErrorMetaDto.class);
65+
66+
assertThat(errorMeta.getId(), is(requiredErrorMeta.getId()));
67+
assertThat(errorMeta.getValue(), is(requiredErrorMeta.getValue()));
68+
assertThat(errorMeta.getCodes().size(), is(2));
69+
}
70+
5971
protected void assertErrorResponse(final @NonNull Response<?> response) {
6072
assertThat(response.getData(), nullValue());
6173

@@ -91,4 +103,24 @@ protected void assertThatAttributesIdFieldIsPresentInObject(final @NonNull Objec
91103

92104
assertThat(idAttributeField, notNullValue());
93105
}
106+
107+
protected ErrorMetaDto buildTestErrorMeta() {
108+
return new ErrorMetaDto(
109+
UUID.randomUUID(),
110+
"Test meta value",
111+
Set.of(
112+
"Value 1",
113+
"Value 2"
114+
)
115+
);
116+
}
117+
118+
@Data
119+
@AllArgsConstructor
120+
@NoArgsConstructor
121+
protected static class ErrorMetaDto {
122+
private UUID id;
123+
private String value;
124+
private Set<String> codes;
125+
}
94126
}

src/test/java/com/slmdev/jsonapi/simple/response/ResponseDeserializationTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,23 @@ public void shouldDeserializeResponseWithErrorWithData() throws Exception {
9292
final Response<Data<TestDto>> deserealizedResponse = objectMapper.readValue(json, objectMapper.getTypeFactory().constructParametricType(Response.class, dataType));
9393

9494
assertErrorResponse(deserealizedResponse);
95+
96+
assertThat(deserealizedResponse.getErrors().get(0).getMeta(), nullValue());
97+
}
98+
99+
@Test
100+
public void shouldDeserializeResponseWithErrorWithMetaAndWithData() throws Exception {
101+
final ErrorMetaDto errorMeta = buildTestErrorMeta();
102+
final Response<Data<TestDto>> response = Response.<Data<TestDto>, TestDto>builder()
103+
.data(buildTestDto1())
104+
.error(HttpStatus.BAD_REQUEST, ERROR_CODE, ERROR_DESCRIPTION, new Error.ErrorMeta(errorMeta))
105+
.build();
106+
107+
final String json = objectMapper.writeValueAsString(response);
108+
final JavaType dataType = objectMapper.getTypeFactory().constructParametricType(Data.class, TestDto.class);
109+
final Response<Data<TestDto>> deserealizedResponse = objectMapper.readValue(json, objectMapper.getTypeFactory().constructParametricType(Response.class, dataType));
110+
111+
assertErrorResponse(deserealizedResponse);
112+
assertResponseErrorMeta(deserealizedResponse, errorMeta);
95113
}
96114
}

src/test/java/com/slmdev/jsonapi/simple/response/ResponseTest.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public void shouldReturnResponseWithValidationErrorWithEmptyData() {
206206
}
207207

208208
@Test
209-
public void shouldReturnResponseWithErrorWithLinkAndWith() {
209+
public void shouldReturnResponseWithErrorWithLinkAndWithEmptyData() {
210210
final Error.ErrorLink errorLink = Error.ErrorLink
211211
.builder()
212212
.about("https://example.org/docs/" + ERROR_CODE)
@@ -221,6 +221,19 @@ public void shouldReturnResponseWithErrorWithLinkAndWith() {
221221
assertThat(response.getErrors().get(0).getLinks().getAbout(), is(errorLink.getAbout()));
222222
}
223223

224+
@Test
225+
public void shouldReturnResponseWithErrorCodeAndMetaAndWithData() {
226+
final ErrorMetaDto errorMeta = buildTestErrorMeta();
227+
final Response<Data<TestDto>> response = Response.<Data<TestDto>, TestDto>builder()
228+
.data(buildTestDto1())
229+
.error(HttpStatus.BAD_REQUEST, ERROR_CODE, ERROR_DESCRIPTION, new Error.ErrorMeta(errorMeta))
230+
.build();
231+
232+
assertErrorResponse(response);
233+
assertResponseErrorCode(response);
234+
assertResponseErrorMeta(response, errorMeta);
235+
}
236+
224237
@Test
225238
public void shouldReturnResponseWithEmptyDataAndChangedMetaVersion() {
226239
final Response<Void> response = Response.<Void, Void>builder()

0 commit comments

Comments
 (0)