spring-cloud / spring-cloud/spring-cloud-function

Structured CloudEvent fails to convert to `io.cloudevents.CloudEvent` in the functional model (data lost / "Unknown encoding")

Đang mở Phù hợp với người mới
#1,455 3 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Ngôn ngữ chính
Java
Star
1.1k
Fork
641
Merge trung bình
11 giờ 2 phút
Pull request đã merge (30 ngày)
8

Mô tả

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:

  1. it does not reset the contentType header — the outgoing message keeps application/cloudevents+json even though the payload is no longer the serialized envelope; and
  2. it leaves the extracted data as a deserialized Map, 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 / buildBinaryMessageFromStructuredMap are byte-identical across these.
  • io.cloudevents:cloudevents-spring: 2.2.1 (the optional dependency declared by spring-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:

  1. reset MessageHeaders.CONTENT_TYPE to the data content-type (mirroring the binary-mode branch), and
  2. re-serialize the extracted data to bytes using the data content-type when it is not already byte[]/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.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu tại CloudEventMessageUtils.buildBinaryMessageFromStructuredMap, nơi báo cáo xác định là phần tái dựng các event có cấu trúc, và xem lại nhánh binary của toCanonical để đối chiếu. Bổ sung coverage trong CloudEventFunctionTests cho một event có cấu trúc nhắm đến io.cloudevents.CloudEvent, sau đó chạy các bài kiểm thử CloudEvent. Được xem là hoàn tất khi việc chuyển đổi thành công với các thuộc tính mong đợi và data khác null.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
java, spring
Lĩnh vực
backend
Loại issue
Lỗi
Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
78/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.