Skip to content

Commit 788757c

Browse files
authored
Move additionalProperties from DefaultProblem to Problem (#25)
Fixes #1. Also adds integration tests for jackson (de)serialization: * with the version 2.12.7 as used on JBoss EAP 7.4 * with the latest version 2.17.0
1 parent 7ffd2f6 commit 788757c

File tree

20 files changed

+403
-151
lines changed

20 files changed

+403
-151
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>io.github.belgif.rest.problem</groupId>
8+
<artifactId>belgif-rest-problem-it</artifactId>
9+
<version>${revision}</version>
10+
</parent>
11+
12+
<artifactId>belgif-rest-problem-it-common</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>io.github.belgif.rest.problem</groupId>
17+
<artifactId>belgif-rest-problem</artifactId>
18+
<version>${project.version}</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>com.fasterxml.jackson.core</groupId>
22+
<artifactId>jackson-databind</artifactId>
23+
<scope>provided</scope>
24+
</dependency>
25+
<dependency>
26+
<groupId>org.junit.jupiter</groupId>
27+
<artifactId>junit-jupiter</artifactId>
28+
<scope>provided</scope>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.assertj</groupId>
32+
<artifactId>assertj-core</artifactId>
33+
<scope>provided</scope>
34+
</dependency>
35+
</dependencies>
36+
37+
</project>

belgif-rest-problem-it/belgif-rest-problem-jakarta-ee-it/src/main/java/com/acme/custom/CustomProblem.java renamed to belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/com/acme/custom/CustomProblem.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.acme.custom;
22

33
import java.net.URI;
4+
import java.util.Objects;
45

56
import com.fasterxml.jackson.annotation.JsonCreator;
7+
import com.fasterxml.jackson.annotation.JsonProperty;
68

79
import io.github.belgif.rest.problem.api.ClientProblem;
810
import io.github.belgif.rest.problem.api.ProblemType;
@@ -20,7 +22,7 @@ public class CustomProblem extends ClientProblem {
2022
private String customField;
2123

2224
@JsonCreator
23-
public CustomProblem(String customField) {
25+
public CustomProblem(@JsonProperty("customField") String customField) {
2426
super(TYPE_URI, TITLE, STATUS);
2527
this.customField = customField;
2628
}
@@ -33,4 +35,24 @@ public String getCustomField() {
3335
return customField;
3436
}
3537

38+
@Override
39+
public boolean equals(Object o) {
40+
if (this == o) {
41+
return true;
42+
}
43+
if (o == null || getClass() != o.getClass()) {
44+
return false;
45+
}
46+
if (!super.equals(o)) {
47+
return false;
48+
}
49+
CustomProblem that = (CustomProblem) o;
50+
return Objects.equals(customField, that.customField);
51+
}
52+
53+
@Override
54+
public int hashCode() {
55+
return Objects.hash(super.hashCode(), customField);
56+
}
57+
3658
}

belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/api/ProblemTest.java renamed to belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/AbstractJacksonSerializationTest.java

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,91 @@
1-
package io.github.belgif.rest.problem.api;
1+
package io.github.belgif.rest.problem;
22

33
import static org.assertj.core.api.Assertions.*;
44

55
import java.net.URI;
66
import java.util.Collections;
77

8+
import org.junit.jupiter.api.BeforeAll;
89
import org.junit.jupiter.api.BeforeEach;
910
import org.junit.jupiter.api.Test;
1011

12+
import com.acme.custom.CustomProblem;
1113
import com.fasterxml.jackson.core.JsonProcessingException;
1214
import com.fasterxml.jackson.databind.ObjectMapper;
1315
import com.fasterxml.jackson.databind.SerializationFeature;
16+
import com.fasterxml.jackson.databind.cfg.PackageVersion;
1417

15-
import io.github.belgif.rest.problem.BadRequestProblem;
16-
import io.github.belgif.rest.problem.DefaultProblem;
17-
import io.github.belgif.rest.problem.ProblemModule;
18+
import io.github.belgif.rest.problem.api.InEnum;
19+
import io.github.belgif.rest.problem.api.Input;
20+
import io.github.belgif.rest.problem.api.InputValidationIssue;
21+
import io.github.belgif.rest.problem.api.InputValidationIssues;
22+
import io.github.belgif.rest.problem.api.Problem;
1823
import io.github.belgif.rest.problem.registry.TestProblemTypeRegistry;
1924

20-
class ProblemTest {
25+
abstract class AbstractJacksonSerializationTest {
2126

2227
private ObjectMapper mapper;
2328

29+
@BeforeAll
30+
static void printJacksonVersion() {
31+
print("jackson version: " + PackageVersion.VERSION);
32+
}
33+
2434
@BeforeEach
2535
void setUp() {
2636
mapper = new ObjectMapper();
2737
mapper.enable(SerializationFeature.INDENT_OUTPUT);
2838
TestProblemTypeRegistry registry = new TestProblemTypeRegistry();
29-
registry.registerProblemType(BadRequestProblem.class);
39+
registry.registerProblemType(BadRequestProblem.class, CustomProblem.class, TooManyRequestsProblem.class);
3040
mapper.registerModule(new ProblemModule(registry));
3141
}
3242

3343
@Test
34-
void jacksonRoundtrip() throws JsonProcessingException {
44+
void badRequestProblem() throws JsonProcessingException {
3545
BadRequestProblem problem = new BadRequestProblem();
3646
problem.setDetail("my detail message");
37-
String json = mapper.writeValueAsString(problem);
38-
print(json);
39-
Problem result = mapper.readValue(json, Problem.class);
40-
assertThat(result).isInstanceOf(BadRequestProblem.class);
41-
print(mapper.writeValueAsString(result));
47+
problem.setAdditionalProperty("additional", "property");
48+
assertSerializationRoundtrip(problem);
4249
}
4350

4451
@Test
45-
void jacksonRoundtripSsinReplaced() throws JsonProcessingException {
52+
void customProblem() throws JsonProcessingException {
53+
CustomProblem problem = new CustomProblem("custom");
54+
problem.setAdditionalProperty("additional", "property");
55+
assertSerializationRoundtrip(problem);
56+
}
57+
58+
@Test
59+
void retryAfterProblem() throws JsonProcessingException {
60+
TooManyRequestsProblem problem = new TooManyRequestsProblem();
61+
problem.setRetryAfterSec(60L);
62+
problem.setAdditionalProperty("additional", "property");
63+
assertSerializationRoundtrip(problem);
64+
}
65+
66+
@Test
67+
void badRequestProblemReplacedSsin() throws JsonProcessingException {
4668
BadRequestProblem problem = new BadRequestProblem(
4769
InputValidationIssues.replacedSsin(InEnum.BODY, "parent[1].ssin", "12345678901", "23456789012"));
48-
String json = mapper.writeValueAsString(problem);
49-
print(json);
50-
Problem result = mapper.readValue(json, Problem.class);
51-
assertThat(result).isInstanceOf(BadRequestProblem.class);
52-
assertThat(((BadRequestProblem) result).getIssues().get(0).getAdditionalProperties())
53-
.containsExactly(entry("replacedBy", "23456789012"));
54-
print(mapper.writeValueAsString(result));
70+
assertSerializationRoundtrip(problem);
5571
}
5672

5773
@Test
58-
void jacksonRoundtripMultipleInputs() throws JsonProcessingException {
74+
void badRequestProblemMultipleInputs() throws JsonProcessingException {
5975
BadRequestProblem problem = new BadRequestProblem();
6076
problem.setDetail("my detail message");
6177
InputValidationIssue issue = new InputValidationIssue();
6278
issue.setType(URI.create("urn:problem-type:cbss:input-validation:exactlyOneOfExpected"));
6379
issue.setTitle("Exactly one of these inputs is expected");
6480
issue.setTitle("Exactly one of inputs [one, two] is expected");
65-
issue.addInput(new Input(InEnum.QUERY, "one", 1));
66-
issue.addInput(new Input(InEnum.QUERY, "two", 2));
81+
issue.addInput(Input.query("one", 1));
82+
issue.addInput(Input.query("two", 2));
6783
problem.addIssue(issue);
68-
String json = mapper.writeValueAsString(problem);
69-
print(json);
70-
Problem result = mapper.readValue(json, Problem.class);
71-
assertThat(result).isInstanceOf(BadRequestProblem.class);
72-
assertThat(((BadRequestProblem) result).getIssues().get(0).getInputs()).hasSize(2);
73-
print(mapper.writeValueAsString(result));
84+
assertSerializationRoundtrip(problem);
7485
}
7586

7687
@Test
77-
void jacksonUnmarshallInNameValueWithInputsArray() throws JsonProcessingException {
88+
void badRequestProblemWithInNameValueAndInputsArray() throws JsonProcessingException {
7889
String json = "{\n"
7990
+ " \"type\": \"urn:problem-type:belgif:badRequest\",\n"
8091
+ " \"href\": \"https://www.belgif.be/specification/rest/api-guide/problems/badRequest.html\",\n"
@@ -101,21 +112,22 @@ void jacksonUnmarshallInNameValueWithInputsArray() throws JsonProcessingExceptio
101112
InputValidationIssue issue = ((BadRequestProblem) result).getIssues().get(0);
102113
assertThat(issue.getName()).isEqualTo("test");
103114
assertThat(issue.getInputs().get(0).getName()).isEqualTo("test");
104-
print(mapper.writeValueAsString(result));
115+
assertThat(mapper.writeValueAsString(result)).isEqualToIgnoringWhitespace(json);
105116
}
106117

107118
@Test
108-
void fallbackToDefaultProblemWhenProblemTypeNotMapped() throws JsonProcessingException {
119+
void unmappedProblem() throws JsonProcessingException {
109120
mapper = new ObjectMapper();
110121
mapper.enable(SerializationFeature.INDENT_OUTPUT);
111122

112123
BadRequestProblem problem = new BadRequestProblem();
113124
problem.setDetail("my detail message");
125+
problem.setAdditionalProperty("additional", "property");
114126
String json = mapper.writeValueAsString(problem);
115127
print(json);
116128
Problem result = mapper.readValue(json, Problem.class);
117129
assertThat(result).isInstanceOf(DefaultProblem.class);
118-
print(mapper.writeValueAsString(result));
130+
assertThat(mapper.writeValueAsString(result)).isEqualTo(json);
119131
}
120132

121133
@Test
@@ -137,7 +149,8 @@ void legacyInvalidParamProblem() throws JsonProcessingException {
137149
Problem problem = mapper.readValue(json, Problem.class);
138150
assertThat(problem).isInstanceOf(BadRequestProblem.class);
139151
BadRequestProblem badRequestProblem = (BadRequestProblem) problem;
140-
InvalidParam invalidParam = badRequestProblem.getInvalidParams().get(0);
152+
assertThat(badRequestProblem.getInvalidParams()).isNotEmpty();
153+
assertThat(mapper.writeValueAsString(badRequestProblem)).isEqualToIgnoringWhitespace(json);
141154
}
142155

143156
@Test
@@ -159,6 +172,30 @@ void unknownProblemWithMessage() throws JsonProcessingException {
159172
entry("message", "552-Id Value is invalid"));
160173
}
161174

175+
@Test
176+
void additionalExceptionProperties() throws JsonProcessingException {
177+
BadRequestProblem problem = new BadRequestProblem();
178+
problem.setAdditionalProperty("cause", "cause");
179+
problem.setAdditionalProperty("stackTrace", "stackTrace");
180+
problem.setAdditionalProperty("suppressed", "suppressed");
181+
problem.setAdditionalProperty("message", "message");
182+
problem.setAdditionalProperty("localizedMessage", "localizedMessage");
183+
assertSerializationRoundtrip(problem);
184+
}
185+
186+
void assertSerializationRoundtrip(Problem problem) throws JsonProcessingException {
187+
String json = mapper.writeValueAsString(problem);
188+
print(json);
189+
Problem result = mapper.readValue(json, Problem.class);
190+
assertThat(result).withRepresentation(p -> {
191+
try {
192+
return mapper.writeValueAsString(p);
193+
} catch (JsonProcessingException e) {
194+
throw new RuntimeException(e);
195+
}
196+
}).isEqualTo(problem);
197+
}
198+
162199
private static void print(String value) {
163200
System.out.println(value); // SUPPRESS CHECKSTYLE
164201
}

belgif-rest-problem/src/test/java/io/github/belgif/rest/problem/registry/TestProblemTypeRegistry.java renamed to belgif-rest-problem-it/belgif-rest-problem-it-common/src/main/java/io/github/belgif/rest/problem/registry/TestProblemTypeRegistry.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010

1111
public class TestProblemTypeRegistry implements ProblemTypeRegistry {
1212

13-
private List<NamedType> problemTypes = new ArrayList<>();
13+
private final List<NamedType> problemTypes = new ArrayList<>();
1414

15-
public void registerProblemType(Class<? extends Problem>... problem) {
15+
@SafeVarargs
16+
public final void registerProblemType(Class<? extends Problem>... problem) {
1617
for (Class<? extends Problem> problemType : problem) {
1718
ProblemType annotation = problemType.getAnnotation(ProblemType.class);
1819
if (annotation != null) {
@@ -25,4 +26,5 @@ public void registerProblemType(Class<? extends Problem>... problem) {
2526
public NamedType[] getProblemTypes() {
2627
return problemTypes.toArray(new NamedType[0]);
2728
}
29+
2830
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>io.github.belgif.rest.problem</groupId>
8+
<artifactId>belgif-rest-problem-it</artifactId>
9+
<version>${revision}</version>
10+
</parent>
11+
12+
<artifactId>belgif-rest-problem-jackson-latest-it</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>io.github.belgif.rest.problem</groupId>
17+
<artifactId>belgif-rest-problem-it-common</artifactId>
18+
<version>${project.version}</version>
19+
<scope>test</scope>
20+
</dependency>
21+
<dependency>
22+
<groupId>com.fasterxml.jackson.core</groupId>
23+
<artifactId>jackson-databind</artifactId>
24+
<version>2.17.0</version>
25+
<scope>provided</scope>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.junit.jupiter</groupId>
29+
<artifactId>junit-jupiter</artifactId>
30+
<scope>test</scope>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.assertj</groupId>
34+
<artifactId>assertj-core</artifactId>
35+
<scope>test</scope>
36+
</dependency>
37+
</dependencies>
38+
39+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package io.github.belgif.rest.problem;
2+
3+
class JacksonSerializationLatestVersionTest extends AbstractJacksonSerializationTest {
4+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<configuration>
2+
3+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
4+
<encoder>
5+
<pattern>%d [%thread] %.-1level %logger{36} %M - %msg%n</pattern>
6+
</encoder>
7+
<filter class="ch.qos.logback.classic.filter.LevelFilter">
8+
<level>ERROR</level>
9+
<onMatch>DENY</onMatch>
10+
<onMismatch>ACCEPT</onMismatch>
11+
</filter>
12+
<target>System.out</target>
13+
</appender>
14+
15+
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
16+
<encoder>
17+
<pattern>%d [%thread] %.-1level %logger{36} %M - %msg%n</pattern>
18+
</encoder>
19+
<filter class="ch.qos.logback.classic.filter.LevelFilter">
20+
<level>ERROR</level>
21+
<onMatch>ACCEPT</onMatch>
22+
<onMismatch>DENY</onMismatch>
23+
</filter>
24+
<target>System.err</target>
25+
</appender>
26+
27+
<root level="info">
28+
<appender-ref ref="STDOUT" />
29+
<appender-ref ref="STDERR" />
30+
</root>
31+
32+
</configuration>

0 commit comments

Comments
 (0)