Skip to content

Commit 72ca579

Browse files
api-clients-generation-pipeline[bot]NouemanKHALci.datadog-api-spec
authored
Improve resiliency of Java SDK when deserialising enums/oneOfs (#944)
* add client deserialization cassette * Java Resiliency * add package to UnparsedObject template * fix pojo * Fix setActualInstance to accept UnparsedObject instances * add equals method for enum classes * add getter/setter for UnparsedObject * ignore unparsed attribute on serialization * override UnparsedObject toString method to fix serialization issues on instance fields * override UnparsedObject equals methods * fixes * add custom serializer to the UnparsedObject class * update client resiliency cassette * fixing errors * update lookUp method to try to fetch data when attribute is not found * Add Custom Serializer/Deserializer and getters/setters to modelEnum * remove {{first}} on pojo enum value check * normal Enum, Update Cast for oneof * jackson not using setters, trying with JsonCreator to set unparsed * propagate unparsed in pojo * Fix Handling of primitive types, and propagation of unparsed Value * Fix Mistakes * add Deserialization test * Fix oneof_model generation issue * Adjust Lookup function to handle Map<String, Object> * Code fixes * update Deserialization unit tests * Fix typo in pojo * Fix LookUp function, still needs cleanup overall * Fix Primitive Types OneOf * Add Deserialization Unit Tests * lookup method cleanup * use x-one-of-as-properties * Refactor Deserialization unit tests * fix unparsed propagation on add{{item}} methods * fix unparsed propagation for arrays * fix equals and add hashCode for enum classes * fix isEnum checks to #allowableValues * add hashCode to UnparsedObject class * remove unused import on UnparsedObject * fix equals method for UnparsedObject * Fix oneOf on primitive types * code cleanup * code cleanup pojo * apply suggestion * Regenerate client from commit 04b2dd3 of spec repo Co-authored-by: NouemanKHAL <noueman.khal@gmail.com> Co-authored-by: api-clients-generation-pipeline[bot] <54105614+api-clients-generation-pipeline[bot]@users.noreply.github.com> Co-authored-by: ci.datadog-api-spec <packages@datadoghq.com>
1 parent 9808d1b commit 72ca579

File tree

949 files changed

+19922
-3917
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

949 files changed

+19922
-3917
lines changed

.apigentools-info

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
"spec_versions": {
55
"v1": {
66
"apigentools_version": "1.4.1.dev11",
7-
"regenerated": "2021-08-25 12:36:17.999543",
8-
"spec_repo_commit": "db0de24"
7+
"regenerated": "2021-08-27 15:15:14.535937",
8+
"spec_repo_commit": "04b2dd3"
99
},
1010
"v2": {
1111
"apigentools_version": "1.4.1.dev11",
12-
"regenerated": "2021-08-25 12:36:56.521861",
13-
"spec_repo_commit": "db0de24"
12+
"regenerated": "2021-08-27 15:16:03.083324",
13+
"spec_repo_commit": "04b2dd3"
1414
}
1515
}
1616
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package {{modelPackage}};
2+
3+
import java.util.Map;
4+
import java.io.IOException;
5+
import com.fasterxml.jackson.core.JsonGenerator;
6+
import com.fasterxml.jackson.core.JsonProcessingException;
7+
import com.fasterxml.jackson.annotation.JsonCreator;
8+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
9+
import com.fasterxml.jackson.databind.SerializerProvider;
10+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
11+
12+
@JsonSerialize(using = UnparsedObject.UnparsedObjectSerializer.class)
13+
public class UnparsedObject {
14+
Map<String, Object> data;
15+
16+
public UnparsedObject(Map<String, Object> data) {
17+
this.data = data;
18+
}
19+
20+
public static class UnparsedObjectSerializer extends StdSerializer<UnparsedObject> {
21+
public UnparsedObjectSerializer(Class<UnparsedObject> t) {
22+
super(t);
23+
}
24+
25+
public UnparsedObjectSerializer() {
26+
this(null);
27+
}
28+
29+
@Override
30+
public void serialize(UnparsedObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
31+
jgen.writeObject(value.data);
32+
}
33+
}
34+
35+
public Map<String, Object> getData() {
36+
return this.data;
37+
}
38+
39+
public void setData(Map<String, Object> data) {
40+
this.data = data;
41+
}
42+
43+
@Override
44+
public int hashCode() {
45+
return data.hashCode();
46+
}
47+
48+
/**
49+
* Return true if this UnparsedObject object is equal to o.
50+
*/
51+
@Override
52+
public boolean equals(Object o) {
53+
if (this == o) {
54+
return true;
55+
}
56+
if (o == null || getClass() != o.getClass()) {
57+
return false;
58+
}
59+
return this.data.equals(((UnparsedObject)o).data);
60+
}
61+
62+
@JsonCreator
63+
public static UnparsedObject fromValue(Map<String, Object> value) {
64+
return new UnparsedObject(value);
65+
}
66+
}

.generator/templates/libraries/jersey2/model.mustache

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import java.io.Serializable;
2929
{{#jackson}}
3030
import com.fasterxml.jackson.annotation.JsonCreator;
3131
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
32+
import com.fasterxml.jackson.annotation.JsonIgnore;
3233
{{#withXml}}
3334
import com.fasterxml.jackson.dataformat.xml.annotation.*;
3435
{{/withXml}}

.generator/templates/libraries/jersey2/oneof_model.mustache

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.fasterxml.jackson.core.JsonParser;
1212
import com.fasterxml.jackson.core.JsonProcessingException;
1313
import com.fasterxml.jackson.core.JsonToken;
1414
import com.fasterxml.jackson.core.type.TypeReference;
15+
import com.fasterxml.jackson.databind.ObjectMapper;
1516
import com.fasterxml.jackson.databind.DeserializationContext;
1617
import com.fasterxml.jackson.databind.JsonMappingException;
1718
import com.fasterxml.jackson.databind.JsonNode;
@@ -21,14 +22,19 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2122
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2223
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
2324
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
25+
import com.fasterxml.jackson.annotation.JsonIgnore;
2426
import {{invokerPackage}}.JSON;
27+
import {{{package}}}.UnparsedObject;
2528

2629
{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>xmlAnnotation}}
2730
@JsonDeserialize(using = {{classname}}.{{classname}}Deserializer.class)
2831
@JsonSerialize(using = {{classname}}.{{classname}}Serializer.class)
2932
public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}}, {{{.}}}{{/vendorExtensions.x-implements}} {
3033
private static final Logger log = Logger.getLogger({{classname}}.class.getName());
3134

35+
@JsonIgnore
36+
public boolean unparsed = false;
37+
3238
public static class {{classname}}Serializer extends StdSerializer<{{classname}}> {
3339
public {{classname}}Serializer(Class<{{classname}}> t) {
3440
super(t);
@@ -57,6 +63,7 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
5763
public {{classname}} deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
5864
JsonNode tree = jp.readValueAsTree();
5965
Object deserialized = null;
66+
Object tmp = null;
6067
{{#useOneOfDiscriminatorLookup}}
6168
{{#discriminator}}
6269
{{classname}} new{{classname}} = new {{classname}}();
@@ -78,43 +85,54 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
7885
boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS);
7986
int match = 0;
8087
JsonToken token = tree.traverse(jp.getCodec()).nextToken();
81-
{{#oneOf}}
82-
// deserialize {{{.}}}
88+
{{#vendorExtensions.x-oneOf-as-properties}}
89+
// deserialize {{dataType}}
8390
try {
8491
boolean attemptParsing = true;
8592
// ensure that we respect type coercion as set on the client ObjectMapper
86-
if ({{{.}}}.class.equals(Integer.class) || {{{.}}}.class.equals(Long.class) || {{{.}}}.class.equals(Float.class) || {{{.}}}.class.equals(Double.class) || {{{.}}}.class.equals(Boolean.class) || {{{.}}}.class.equals(String.class)) {
93+
if ({{dataType}}.class.equals(Integer.class) || {{dataType}}.class.equals(Long.class) || {{dataType}}.class.equals(Float.class) || {{dataType}}.class.equals(Double.class) || {{dataType}}.class.equals(Boolean.class) || {{dataType}}.class.equals(String.class)) {
8794
attemptParsing = typeCoercion;
8895
if (!attemptParsing) {
89-
attemptParsing |= (({{{.}}}.class.equals(Integer.class) || {{{.}}}.class.equals(Long.class)) && token == JsonToken.VALUE_NUMBER_INT);
90-
attemptParsing |= (({{{.}}}.class.equals(Float.class) || {{{.}}}.class.equals(Double.class)) && (token == JsonToken.VALUE_NUMBER_FLOAT || token == JsonToken.VALUE_NUMBER_INT));
91-
attemptParsing |= ({{{.}}}.class.equals(Boolean.class) && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE));
92-
attemptParsing |= ({{{.}}}.class.equals(String.class) && token == JsonToken.VALUE_STRING);
96+
attemptParsing |= (({{dataType}}.class.equals(Integer.class) || {{dataType}}.class.equals(Long.class)) && token == JsonToken.VALUE_NUMBER_INT);
97+
attemptParsing |= (({{dataType}}.class.equals(Float.class) || {{dataType}}.class.equals(Double.class)) && (token == JsonToken.VALUE_NUMBER_FLOAT || token == JsonToken.VALUE_NUMBER_INT));
98+
attemptParsing |= ({{dataType}}.class.equals(Boolean.class) && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE));
99+
attemptParsing |= ({{dataType}}.class.equals(String.class) && token == JsonToken.VALUE_STRING);
93100
{{#isNullable}}
94101
attemptParsing |= (token == JsonToken.VALUE_NULL);
95102
{{/isNullable}}
96103
}
97104
}
98105
if (attemptParsing) {
99-
deserialized = tree.traverse(jp.getCodec()).readValueAs({{{.}}}.class);
106+
tmp = tree.traverse(jp.getCodec()).readValueAs({{dataType}}.class);
100107
// TODO: there is no validation against JSON schema constraints
101108
// (min, max, enum, pattern...), this does not perform a strict JSON
102109
// validation, which means the 'match' count may be higher than it should be.
110+
{{^isPrimitiveType}}
111+
if (!(({{dataType}})tmp).unparsed) {
112+
deserialized = tmp;
113+
match++;
114+
}
115+
{{/isPrimitiveType}}
116+
{{#isPrimitiveType}}
117+
deserialized = tmp;
103118
match++;
104-
log.log(Level.FINER, "Input data matches schema '{{{.}}}'");
119+
{{/isPrimitiveType}}
120+
log.log(Level.FINER, "Input data matches schema '{{dataType}}'");
105121
}
106122
} catch (Exception e) {
107123
// deserialization failed, continue
108-
log.log(Level.FINER, "Input data does not match schema '{{{.}}}'", e);
124+
log.log(Level.FINER, "Input data does not match schema '{{dataType}}'", e);
109125
}
110126

111-
{{/oneOf}}
127+
{{/vendorExtensions.x-oneOf-as-properties}}
128+
{{classname}} ret = new {{classname}}();
112129
if (match == 1) {
113-
{{classname}} ret = new {{classname}}();
114130
ret.setActualInstance(deserialized);
115-
return ret;
131+
} else {
132+
Map<String, Object> res = new ObjectMapper().readValue(tree.traverse(jp.getCodec()).readValueAsTree().toString(), HashMap.class);
133+
ret.setActualInstance(new UnparsedObject(res));
116134
}
117-
throw new IOException(String.format("Failed deserialization for {{classname}}: %d classes match result, expected 1", match));
135+
return ret;
118136
}
119137

120138
/**
@@ -204,6 +222,12 @@ public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-im
204222
return;
205223
}
206224

225+
{{#-last}}
226+
if (JSON.isInstanceOf(UnparsedObject.class, instance, new HashSet<Class<?>>())) {
227+
super.setActualInstance(instance);
228+
return;
229+
}
230+
{{/-last}}
207231
{{/oneOf}}
208232
throw new RuntimeException("Invalid instance type. Must be {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}");
209233
}

.generator/templates/libraries/jersey2/pojo.mustache

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
{{/jackson}}
1212
{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}}
1313
public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{
14+
@JsonIgnore
15+
public boolean unparsed = false;
1416
{{#serializableModel}}
1517
private static final long serialVersionUID = 1L;
1618

@@ -107,7 +109,9 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
107109
{{/requiredVars}}
108110
) {
109111
{{#requiredVars}}
110-
this.{{name}} = {{name}};
112+
this.{{name}} = {{name}};{{#allowableValues}}
113+
this.unparsed |= !{{name}}.isValid();{{/allowableValues}}{{#isModel}}{{^isPrimitiveType}}
114+
this.unparsed |= {{name}}.unparsed;{{/isPrimitiveType}}{{/isModel}}
111115
{{/requiredVars}}
112116
}
113117
{{/hasRequired}}
@@ -132,6 +136,36 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
132136
{{/vendorExtensions.x-is-jackson-optional-nullable}}
133137
{{^vendorExtensions.x-is-jackson-optional-nullable}}
134138
this.{{name}} = {{name}};
139+
{{^isArray}}
140+
{{#allowableValues}}
141+
{{^isPrimitiveType}}
142+
this.unparsed |= !{{name}}.isValid();
143+
{{/isPrimitiveType}}
144+
{{/allowableValues}}
145+
{{#isModel}}
146+
{{^isPrimitiveType}}
147+
this.unparsed |= {{name}}.unparsed;
148+
{{/isPrimitiveType}}
149+
{{/isModel}}
150+
{{/isArray}}
151+
{{#isArray}}
152+
{{#items.isModel}}
153+
{{^items.isPrimitiveType}}
154+
for ({{items.dataType}} item : {{name}}) {
155+
{{#items.allowableValues}}
156+
{{^items.isPrimitiveType}}
157+
this.unparsed |= !item.isValid();
158+
{{/items.isPrimitiveType}}
159+
{{/items.allowableValues}}
160+
{{#items.isModel}}
161+
{{^items.isPrimitiveType}}
162+
this.unparsed |= item.unparsed;
163+
{{/items.isPrimitiveType}}
164+
{{/items.isModel}}
165+
}
166+
{{/items.isPrimitiveType}}
167+
{{/items.isModel}}
168+
{{/isArray}}
135169
{{/vendorExtensions.x-is-jackson-optional-nullable}}
136170
return this;
137171
}
@@ -156,6 +190,16 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
156190
}
157191
{{/required}}
158192
this.{{name}}.add({{name}}Item);
193+
{{#items.allowableValues}}
194+
{{^isPrimitiveType}}
195+
this.unparsed |= !{{name}}Item.isValid();
196+
{{/isPrimitiveType}}
197+
{{/items.allowableValues}}
198+
{{#items.isModel}}
199+
{{^isPrimitiveType}}
200+
this.unparsed |= {{name}}Item.unparsed;
201+
{{/isPrimitiveType}}
202+
{{/items.isModel}}
159203
return this;
160204
{{/vendorExtensions.x-is-jackson-optional-nullable}}
161205
}
@@ -254,6 +298,11 @@ public class {{classname}} {{#parent}}extends {{{parent}}} {{/parent}}{{#vendorE
254298
}
255299
256300
{{/vendorExtensions.x-enum-as-string}}
301+
{{#allowableValues}}
302+
if (!{{name}}.isValid()) {
303+
this.unparsed = true;
304+
}
305+
{{/allowableValues}}
257306
{{#vendorExtensions.x-is-jackson-optional-nullable}}
258307
this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}});
259308
{{/vendorExtensions.x-is-jackson-optional-nullable}}

0 commit comments

Comments
 (0)