Skip to content

Commit 5e3bfc8

Browse files
authored
Specification Compliant handling of numeric context attributes (#358)
* - Added tests case to verify expected handling of numeric context attributes - Updated serializer. Signed-off-by: Day, Jeremy(jday) <[email protected]> * - Added @deprecated marker for CloudEventContextWriter.set(name, Number) - Added use of new method for JSON serializer. Cleanup of deprecated implementations can occur independantly. Signed-off-by: Day, Jeremy(jday) <[email protected]> * Addressed Review Comments - Now throws exception when non specification compliant numeric attribute values are received during deserialization. - Added test cases to verify deserialization exceptions. Signed-off-by: Day, Jeremy(jday) <[email protected]> * Address Review Comments Signed-off-by: Day, Jeremy(jday) <[email protected]> * Address Review Comment Signed-off-by: Day, Jeremy(jday) <[email protected]>
1 parent 13f8b56 commit 5e3bfc8

File tree

13 files changed

+174
-7
lines changed

13 files changed

+174
-7
lines changed

api/src/main/java/io/cloudevents/rw/CloudEventContextWriter.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,29 @@ default CloudEventContextWriter withContextAttribute(String name, OffsetDateTime
8383
* @return self
8484
* @throws CloudEventRWException if anything goes wrong while writing this extension.
8585
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
86+
*
87+
* @deprecated CloudEvent specification only permits {@link Integer} type as a
88+
* numeric value.
8689
*/
8790
default CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
8891
return withContextAttribute(name, value.toString());
8992
}
9093

94+
/**
95+
* Set attribute with type {@link Integer}.
96+
* This setter should not be invoked for specversion, because the writer should
97+
* already know the specversion or because it doesn't need it to correctly write the value.
98+
*
99+
* @param name name of the attribute
100+
* @param value value of the attribute
101+
* @return self
102+
* @throws CloudEventRWException if anything goes wrong while writing this extension.
103+
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
104+
*/
105+
default CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException {
106+
return withContextAttribute(name, value.toString());
107+
}
108+
91109
/**
92110
* Set attribute with type {@link Boolean} attribute.
93111
* This setter should not be invoked for specversion, because the writer should

api/src/main/java/io/cloudevents/rw/CloudEventRWException.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ public static CloudEventRWException newInvalidAttributeType(String attributeName
137137
);
138138
}
139139

140+
public static CloudEventRWException newInvalidAttributeType(String attributeName,Object value){
141+
return new CloudEventRWException(
142+
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_TYPE,
143+
"Invalid attribute/extension type for \""
144+
+ attributeName
145+
+ "\": Type" + value.getClass().getCanonicalName()
146+
+ ". Value: " + value
147+
148+
);
149+
}
150+
140151
/**
141152
* @param attributeName the invalid attribute name
142153
* @param value the value of the attribute

core/src/main/java/io/cloudevents/core/v03/CloudEventBuilder.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,26 @@ public CloudEventContextWriter withContextAttribute(String name, Number value) t
246246
}
247247
}
248248

249+
@Override
250+
public CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException
251+
{
252+
requireValidAttributeWrite(name);
253+
switch (name) {
254+
case TIME:
255+
case SCHEMAURL:
256+
case ID:
257+
case TYPE:
258+
case DATACONTENTTYPE:
259+
case DATACONTENTENCODING:
260+
case SUBJECT:
261+
case SOURCE:
262+
throw CloudEventRWException.newInvalidAttributeType(name, Integer.class);
263+
default:
264+
withExtension(name, value);
265+
return this;
266+
}
267+
}
268+
249269
@Override
250270
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
251271
requireValidAttributeWrite(name);

core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,25 @@ public CloudEventContextWriter withContextAttribute(String name, Number value) t
238238
}
239239
}
240240

241+
@Override
242+
public CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException
243+
{
244+
requireValidAttributeWrite(name);
245+
switch (name) {
246+
case TIME:
247+
case DATASCHEMA:
248+
case ID:
249+
case TYPE:
250+
case DATACONTENTTYPE:
251+
case SUBJECT:
252+
case SOURCE:
253+
throw CloudEventRWException.newInvalidAttributeType(name, Integer.class);
254+
default:
255+
withExtension(name, value);
256+
return this;
257+
}
258+
}
259+
241260
@Override
242261
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
243262
requireValidAttributeWrite(name);

core/src/test/java/io/cloudevents/core/test/Data.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.cloudevents.core.builder.CloudEventBuilder;
2222
import io.cloudevents.types.Time;
2323

24+
import java.math.BigDecimal;
2425
import java.net.URI;
2526
import java.time.OffsetDateTime;
2627
import java.util.Objects;
@@ -116,6 +117,16 @@ public class Data {
116117
.withExtension("binary", BINARY_VALUE)
117118
.build();
118119

120+
public static final CloudEvent V1_WITH_NUMERIC_EXT = CloudEventBuilder.v1()
121+
.withId(ID)
122+
.withType(TYPE)
123+
.withSource(SOURCE)
124+
.withExtension("integer", 42)
125+
.withExtension("decimal", new BigDecimal("42.42"))
126+
.withExtension("float", 4.2f)
127+
.withExtension("long", new Long(4200))
128+
.build();
129+
119130
public static final CloudEvent V03_MIN = CloudEventBuilder.v03(V1_MIN).build();
120131
public static final CloudEvent V03_WITH_JSON_DATA = CloudEventBuilder.v03(V1_WITH_JSON_DATA).build();
121132
public static final CloudEvent V03_WITH_JSON_DATA_WITH_EXT = CloudEventBuilder.v03(V1_WITH_JSON_DATA_WITH_EXT).build();

formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,17 @@ public <T extends CloudEventWriter<V>, V> V read(CloudEventWriterFactory<T, V> w
134134
writer.withContextAttribute(extensionName, extensionValue.booleanValue());
135135
break;
136136
case NUMBER:
137-
writer.withContextAttribute(extensionName, extensionValue.numberValue());
137+
138+
final Number numericValue = extensionValue.numberValue();
139+
140+
// Only 'Int' values are supported by the specification
141+
142+
if (numericValue instanceof Integer){
143+
writer.withContextAttribute(extensionName, (Integer) numericValue);
144+
} else{
145+
throw CloudEventRWException.newInvalidAttributeType(extensionName,numericValue);
146+
}
147+
138148
break;
139149
case STRING:
140150
writer.withContextAttribute(extensionName, extensionValue.textValue());

formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventSerializer.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,23 @@ public CloudEventContextWriter withContextAttribute(String name, String value) t
6565
}
6666

6767
@Override
68-
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
68+
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException
69+
{
70+
// Only Integer types are supported by the specification
71+
if (value instanceof Integer) {
72+
this.withContextAttribute(name, (Integer) value);
73+
} else {
74+
// Default to string representation for other numeric values
75+
this.withContextAttribute(name, value.toString());
76+
}
77+
return this;
78+
}
79+
80+
@Override
81+
public CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException
82+
{
6983
try {
70-
gen.writeFieldName(name);
71-
provider.findValueSerializer(value.getClass()).serialize(value, gen, provider);
84+
gen.writeNumberField(name, value.intValue());
7285
return this;
7386
} catch (IOException e) {
7487
throw new RuntimeException(e);

formats/json-jackson/src/test/java/io/cloudevents/jackson/JsonFormatTest.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@
2424
import io.cloudevents.CloudEvent;
2525
import io.cloudevents.SpecVersion;
2626
import io.cloudevents.core.builder.CloudEventBuilder;
27+
import io.cloudevents.core.format.EventDeserializationException;
2728
import io.cloudevents.core.provider.EventFormatProvider;
2829
import io.cloudevents.rw.CloudEventRWException;
30+
import org.junit.jupiter.api.Assertions;
2931
import org.junit.jupiter.api.Test;
3032
import org.junit.jupiter.params.ParameterizedTest;
3133
import org.junit.jupiter.params.provider.Arguments;
3234
import org.junit.jupiter.params.provider.MethodSource;
3335

3436
import java.io.IOException;
37+
import java.math.BigInteger;
3538
import java.net.URISyntaxException;
3639
import java.nio.charset.StandardCharsets;
3740
import java.nio.file.Files;
@@ -40,8 +43,7 @@
4043
import java.util.stream.Stream;
4144

4245
import static io.cloudevents.core.test.Data.*;
43-
import static org.assertj.core.api.Assertions.assertThat;
44-
import static org.assertj.core.api.Assertions.assertThatCode;
46+
import static org.assertj.core.api.Assertions.*;
4547

4648
class JsonFormatTest {
4749

@@ -120,6 +122,21 @@ void throwExpectedOnInvalidSpecversion() {
120122
.hasMessageContaining(CloudEventRWException.newInvalidSpecVersion("9000.1").getMessage());
121123
}
122124

125+
@ParameterizedTest
126+
@MethodSource("badJsonContent")
127+
/**
128+
* JSON content that should fail deserialization
129+
* as it represents content that is not CE
130+
* specification compliant.
131+
*/
132+
void verifyDeserializeError(String inputFile){
133+
134+
byte[] input = loadFile(inputFile);
135+
136+
assertThatExceptionOfType(EventDeserializationException.class).isThrownBy(() -> getFormat().deserialize(input));
137+
138+
}
139+
123140
public static Stream<Arguments> serializeTestArgumentsDefault() {
124141
return Stream.of(
125142
Arguments.of(V03_MIN, "v03/min.json"),
@@ -133,7 +150,8 @@ public static Stream<Arguments> serializeTestArgumentsDefault() {
133150
Arguments.of(V1_WITH_JSON_DATA_WITH_EXT, "v1/json_data_with_ext.json"),
134151
Arguments.of(V1_WITH_XML_DATA, "v1/base64_xml_data.json"),
135152
Arguments.of(V1_WITH_TEXT_DATA, "v1/base64_text_data.json"),
136-
Arguments.of(V1_WITH_BINARY_EXT, "v1/binary_attr.json")
153+
Arguments.of(V1_WITH_BINARY_EXT, "v1/binary_attr.json"),
154+
Arguments.of(V1_WITH_NUMERIC_EXT,"v1/numeric_ext.json")
137155
);
138156
}
139157

@@ -200,6 +218,15 @@ public static Stream<String> roundTripTestArguments() {
200218
);
201219
}
202220

221+
public static Stream<String> badJsonContent() {
222+
return Stream.of(
223+
"v03/fail_numeric_decimal.json",
224+
"v03/fail_numeric_long.json",
225+
"v1/fail_numeric_decimal.json",
226+
"v1/fail_numeric_long.json"
227+
);
228+
}
229+
203230
private JsonFormat getFormat() {
204231
return (JsonFormat) EventFormatProvider.getInstance().resolveFormat(JsonFormat.CONTENT_TYPE);
205232
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"specversion": "1.0",
3+
"id": "1",
4+
"type": "mock.test",
5+
"source": "http://localhost/source",
6+
"decimal": 42.42
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"specversion": "1.0",
3+
"id": "1",
4+
"type": "mock.test",
5+
"source": "http://localhost/source",
6+
"long": 4247483647
7+
}

0 commit comments

Comments
 (0)