Skip to content

Commit 89d1836

Browse files
committed
fix: hydrate empty Event source via Jackson deserializer
1 parent f87f52e commit 89d1836

7 files changed

Lines changed: 100 additions & 2 deletions

File tree

.openapi-generator-ignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ git_push.sh
44
api/*
55
src/test/java/com/fingerprint/v4/model/*
66
src/main/java/com/fingerprint/v4/model/AbstractOpenApiSchema.java
7+
src/main/java/com/fingerprint/v4/sdk/EventDeserializer.java
8+
src/main/java/com/fingerprint/v4/sdk/EventMixin.java
79
src/main/AndroidManifest.xml
810
.github/workflows/maven.yml
911
pom.xml

sdk/src/main/java/com/fingerprint/v4/model/Event.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@
2424
use = JsonTypeInfo.Id.NAME,
2525
include = JsonTypeInfo.As.PROPERTY,
2626
property = "source",
27-
// SPIKE INTER-2457 option B: missing source hydrates to EventDevice. Never rewrite source:edge.
28-
defaultImpl = EventDevice.class,
27+
defaultImpl = Event.UnknownEvent.class,
2928
visible = true)
3029
@JsonSubTypes({
3130
@JsonSubTypes.Type(value = EventDevice.class, name = "device"),
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.fingerprint.v4.sdk;
2+
3+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
4+
import com.fasterxml.jackson.core.JsonParser;
5+
import com.fasterxml.jackson.databind.DeserializationContext;
6+
import com.fasterxml.jackson.databind.JsonDeserializer;
7+
import com.fasterxml.jackson.databind.JsonMappingException;
8+
import com.fasterxml.jackson.databind.JsonNode;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
11+
import com.fasterxml.jackson.databind.node.ObjectNode;
12+
import com.fingerprint.v4.model.Event;
13+
import com.fingerprint.v4.model.EventDevice;
14+
import com.fingerprint.v4.model.EventEdge;
15+
import java.io.IOException;
16+
17+
/**
18+
* Treats a missing, null, or empty Event {@code source} as {@code device}.
19+
* Unknown non-empty values fail. Never rewrites {@code edge}.
20+
*/
21+
public final class EventDeserializer extends JsonDeserializer<Event> {
22+
@Override
23+
public Event deserialize(JsonParser parser, DeserializationContext context) throws IOException {
24+
JsonNode node = parser.getCodec().readTree(parser);
25+
if (node == null || !node.isObject()) {
26+
throw JsonMappingException.from(parser, "event JSON must be an object");
27+
}
28+
29+
ObjectNode object = (ObjectNode) node;
30+
JsonNode source = object.get("source");
31+
if (source == null || source.isNull() || (source.isTextual() && source.asText().isEmpty())) {
32+
object.put("source", "device");
33+
source = object.get("source");
34+
}
35+
36+
if (!source.isTextual()) {
37+
throw JsonMappingException.from(parser, "unknown Event source: " + source);
38+
}
39+
40+
String value = source.asText();
41+
ObjectMapper implMapper = ((ObjectMapper) parser.getCodec()).copy();
42+
implMapper.addMixIn(Event.class, StripEventPolymorphism.class);
43+
if ("device".equals(value)) {
44+
return implMapper.treeToValue(object, EventDevice.class);
45+
}
46+
if ("edge".equals(value)) {
47+
return implMapper.treeToValue(object, EventEdge.class);
48+
}
49+
throw JsonMappingException.from(parser, "unknown Event source: " + value);
50+
}
51+
52+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
53+
@JsonDeserialize(using = JsonDeserializer.None.class)
54+
abstract static class StripEventPolymorphism {}
55+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.fingerprint.v4.sdk;
2+
3+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
4+
5+
@JsonDeserialize(using = EventDeserializer.class)
6+
public abstract class EventMixin {}

sdk/src/main/java/com/fingerprint/v4/sdk/JSON.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
import com.fasterxml.jackson.annotation.*;
1616
import com.fasterxml.jackson.databind.*;
1717
import com.fasterxml.jackson.databind.json.JsonMapper;
18+
import com.fasterxml.jackson.databind.module.SimpleModule;
1819
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
20+
import com.fingerprint.v4.model.Event;
1921
import jakarta.ws.rs.core.GenericType;
2022
import jakarta.ws.rs.ext.ContextResolver;
2123
import java.text.DateFormat;
@@ -45,6 +47,10 @@ public JSON() {
4547
.defaultDateFormat(new RFC3339DateFormat())
4648
.addModule(new JavaTimeModule())
4749
.addModule(new RFC3339JavaTimeModule())
50+
.addModule(
51+
new SimpleModule()
52+
.setMixInAnnotation(Event.class, EventMixin.class)
53+
.addDeserializer(Event.class, new EventDeserializer()))
4854
.build();
4955
}
5056

sdk/src/test/java/com/fingerprint/v4/SerializationTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
66
import static org.junit.jupiter.api.Assertions.assertNotEquals;
77
import static org.junit.jupiter.api.Assertions.assertNotNull;
8+
import static org.junit.jupiter.api.Assertions.assertThrows;
89

910
import com.fasterxml.jackson.databind.ObjectMapper;
1011
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -124,6 +125,29 @@ public void missingSourceHydratesToEventDeviceAndSourceEdgeStaysEventEdge() thro
124125
assertInstanceOf(EventEdge.class, edge);
125126
}
126127

128+
@Test
129+
public void emptySourceHydratesToEventDevice() throws IOException {
130+
ObjectMapper sdkObjectMapper = JSON.getDefault().getMapper();
131+
ObjectNode deviceNode =
132+
sdkObjectMapper.readValue(
133+
getFileAsIOStream("mocks/events/get_event_200.json"), ObjectNode.class);
134+
deviceNode.put("source", "");
135+
Event event = sdkObjectMapper.treeToValue(deviceNode, Event.class);
136+
assertInstanceOf(EventDevice.class, event);
137+
}
138+
139+
@Test
140+
public void unknownSourceFails() throws IOException {
141+
ObjectMapper sdkObjectMapper = JSON.getDefault().getMapper();
142+
ObjectNode deviceNode =
143+
sdkObjectMapper.readValue(
144+
getFileAsIOStream("mocks/events/get_event_200.json"), ObjectNode.class);
145+
deviceNode.put("source", "webhook");
146+
assertThrows(
147+
com.fasterxml.jackson.databind.JsonMappingException.class,
148+
() -> sdkObjectMapper.treeToValue(deviceNode, Event.class));
149+
}
150+
127151
@TestFactory
128152
public Stream<DynamicTest> deserializeErrorResponses() {
129153
ObjectMapper sdkObjectMapper = JSON.getDefault().getMapper();

template/libraries/jersey3/JSON.mustache

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package {{invokerPackage}};
55
import com.fasterxml.jackson.annotation.*;
66
import com.fasterxml.jackson.databind.*;
77
import com.fasterxml.jackson.databind.json.JsonMapper;
8+
import com.fasterxml.jackson.databind.module.SimpleModule;
9+
import com.fingerprint.v4.model.Event;
810
{{#openApiNullable}}
911
import org.openapitools.jackson.nullable.JsonNullableModule;
1012
{{/openApiNullable}}
@@ -44,6 +46,10 @@ public class JSON implements ContextResolver<ObjectMapper> {
4446
.addModule(new JsonNullableModule())
4547
{{/openApiNullable}}
4648
.addModule(new RFC3339JavaTimeModule())
49+
.addModule(
50+
new SimpleModule()
51+
.setMixInAnnotation(Event.class, EventMixin.class)
52+
.addDeserializer(Event.class, new EventDeserializer()))
4753
.build();
4854
}
4955

0 commit comments

Comments
 (0)