spring-cloud / spring-cloud/spring-cloud-function
Structured CloudEvent fails to convert to `io.cloudevents.CloudEvent` in the functional model (data lost / "Unknown encoding")
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- Java
- Estrellas
- 1.1k
- Forks
- 641
- Merge medio
- 11 h 2 min
- PR fusionados (30 d)
- 8
Descripción
Describe the bug
When a structured-mode CloudEvent (Content-Type: application/cloudevents+json, whole envelope in the body) is consumed by a function whose target type is the CNCF io.cloudevents.CloudEvent, message conversion fails.
CloudEventMessageUtils.toCanonical(...) transforms the structured message into a binary-shaped message (data extracted to the payload, attributes moved to ce-* headers), but:
- it does not reset the
contentTypeheader — the outgoing message keepsapplication/cloudevents+jsoneven though the payload is no longer the serialized envelope; and - it leaves the extracted
dataas a deserializedMap, not serialized bytes.
Downstream, the CNCF io.cloudevents.spring.messaging.CloudEventMessageConverter re-decides structured-vs-binary purely from contentType. Because the stale application/cloudevents+json remains, it takes the structured reader and tries to re-parse the (already extracted) payload as the envelope:
java.lang.IllegalArgumentException: argument "src" is null
at io.cloudevents.jackson.JsonFormat.deserialize(JsonFormat.java:158)
at io.cloudevents.spring.messaging.CloudEventMessageConverter.fromMessage(CloudEventMessageConverter.java:45)
at org.springframework.cloud.function.context.config.SmartCompositeMessageConverter.fromMessage(...)
at ...SimpleFunctionRegistry$FunctionInvocationWrapper.convertInputMessageIfNecessary(...)
and
io.cloudevents.rw.CloudEventRWException: Could not parse. Unknown encoding. Invalid content type or spec version
at io.cloudevents.core.message.impl.MessageUtils.parseStructuredOrBinaryMessage(MessageUtils.java:69)
at io.cloudevents.spring.messaging.CloudEventMessageConverter.createMessageReader(CloudEventMessageConverter.java:60)
at io.cloudevents.spring.messaging.CloudEventMessageConverter.fromMessage(CloudEventMessageConverter.java:45)
at org.springframework.cloud.function.context.config.SmartCompositeMessageConverter.fromMessage(SmartCompositeMessageConverter.java:137)
at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.convertInputMessageIfNecessary(SimpleFunctionRegistry.java:1488)
Even after the contentType is corrected so the binary reader is selected, a second symptom remains: the CNCF binary reader only reads byte[]/String payloads (CloudEventMessageConverter#getBinaryData returns null for anything else), so the Map payload is dropped and the delivered cloudEvent.getData() is null while the attributes are present.
This only affects the io.cloudevents.CloudEvent target type. A plain POJO target works, because Spring's own message converters can bind a Map payload to a POJO — which is why the existing CloudEventFunctionTests#testStructuredPojoToPojoDefaultOutputAttributeProvider passes and this case is not covered.
Affected versions
spring-cloud-function-context:4.3.x.CloudEventMessageUtils.toCanonical/buildBinaryMessageFromStructuredMapare byte-identical across these.io.cloudevents:cloudevents-spring:2.2.1(the optional dependency declared byspring-cloud-function-context);- Java: 21.
Sample CloudEvent (structured mode)
Content-Type: application/cloudevents+json, body:
{
"specversion" : "1.0",
"type" : "org.springframework",
"source" : "https://spring.io/",
"id" : "A234-1234-1234",
"datacontenttype" : "application/json",
"data" : {
"version" : "1.0",
"releaseName" : "Spring Framework",
"releaseDate" : "24-03-2004"
}
}
Steps to reproduce
Minimal function with an io.cloudevents.CloudEvent input type (the cloudevents-spring converter is auto-registered by ContextFunctionCatalogAutoConfiguration$CloudEventsMessageConverterConfiguration):
@EnableAutoConfiguration
@Configuration
static class TestConfiguration {
@Bean
Function<Message<CloudEvent>, Message<CloudEvent>> echoCloudEvent() {
return Function.identity();
}
}
@Test
void structuredToCloudEventTypedPayload() {
String payload = /* the structured JSON envelope above */;
Function<Object, Object> function = context.getBean(FunctionCatalog.class).lookup("echoCloudEvent");
Message<String> inputMessage = MessageBuilder
.withPayload(payload)
.setHeader(MessageHeaders.CONTENT_TYPE, "application/cloudevents+json")
.build();
// structured mode: attributes are in the body, not the headers
assertThat(CloudEventMessageUtils.isCloudEvent(inputMessage)).isFalse();
Message<CloudEvent> result = (Message<CloudEvent>) function.apply(inputMessage); // throws
}
Actual behavior
function.apply(...) throws CloudEventRWException: Could not parse. Unknown encoding during input conversion. (If contentType is corrected in isolation, the call succeeds but the delivered CloudEvent has getData() == null.)
Expected behavior
The function receives a fully-populated io.cloudevents.CloudEvent — correct specversion, type, source, id, datacontenttype, and non-null data — consistent with how the same structured event is handled when the target type is a POJO.
Root cause
In CloudEventMessageUtils.buildBinaryMessageFromStructuredMap(...), after extracting data and moving attributes to ce-* headers, the original headers are copied back onto the outgoing message — re-applying contentType=application/cloudevents+json — and the data remains a Map. The binary-mode branch of toCanonical already resets contentType to the data content-type; the structured branch does not, and it does not serialize the data.
Proposed fix
In buildBinaryMessageFromStructuredMap, after building the binary-mode message:
- reset
MessageHeaders.CONTENT_TYPEto the data content-type (mirroring the binary-mode branch), and - re-serialize the extracted
datato bytes using the data content-type when it is not alreadybyte[]/String.
With both applied, the structured event converts correctly to io.cloudevents.CloudEvent (attributes + data), and all existing CloudEvent tests still pass. Happy to open a PR with a CloudEventFunctionTests case covering the io.cloudevents.CloudEvent target type.
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
Comienza en CloudEventMessageUtils.buildBinaryMessageFromStructuredMap, que el informe identifica como el encargado de reconstruir eventos estructurados, y revisa la rama binaria de toCanonical para comparar. Añade cobertura en CloudEventFunctionTests para un evento estructurado cuyo destino sea io.cloudevents.CloudEvent y, después, ejecuta las pruebas de CloudEvent. La tarea estará terminada cuando la conversión tenga éxito con los atributos esperados y data no sea null.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- java, spring
- Área
- backend
- Tipo de issue
- Error
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Estado de actividad
- Activo
- Claridad
- Bien especificado
- Aptitud para principiantes
- 78/100