Skip to content

fix: serialize object-valued extension attributes in binary HTTP headers - #665

Closed
shiva wants to merge 1 commit into
cloudevents:mainfrom
shiva:fix/object-extension-headers
Closed

shiva wants to merge 1 commit into
cloudevents:mainfrom
shiva:fix/object-extension-headers

Conversation

@shiva

@shiva shiva commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Written for: the cloudevents/sdk-javascript maintainers and the #646 participants (duglin, kings1ay3r, blackakula, fhirter).

Summary

Lands the one behavioral fix from #646 on its own: object-valued extension
attributes are now serialized as JSON in binary-mode HTTP headers instead of
reaching the wire as [object Object].

Part of #646. Does not close it; see "What remains" below.

Problem

headersFor() copied extension values into the header map as-is. An
object-valued extension therefore reached the HTTP client as an object, and
Node's http module, axios, got and superagent all coerce header values
with String():

ce-traceinfo: [object Object]

The emitter test had been working around this since #661 by stringifying in
test code, which is a sign the fix belonged in the SDK.

Change

  • headersFor() serializes objects with JSON.stringify(). Strings,
    booleans and numbers pass through unchanged. Two lines.
  • Two tests: the header holds the JSON string, and a binary round trip
    through HTTP.toEvent() delivers that string to the receiver.
  • Documentation. The CloudEvents type system has no object type, so
    accepting objects as extension values is an SDK convenience, not spec
    behavior. The README gains an "Extension Attributes" section, the
    CloudEventV1 extension JSDoc and a comment at the call site say the
    same, and all three recommend encoding the value as a string yourself.

Compatibility

  • On the wire this is a fix, not a break. No receiver can depend on
    [object Object].
  • In-process callers that read HTTP.binary(event).headers directly, or
    round-trip through HTTP.toEvent() without a network, now get a JSON
    string where they used to get the object. This matches the declared
    Headers type, which has always said values are strings.
  • Addresses @blackakula's review note: the convenience is now documented as
    non-conformant and discouraged. A deprecation warning with removal in v11
    is planned as a follow-up rather than bundled here.

What remains from #646

Already on main via #661, so dropped from this series: every dependency
bump including uuid 11 (closes the GHSA in #647), the @types/uuid and
@types/cucumber removals, the ValidationError rewrite, and the
JSONParser.parse return type. The as string | Record<...> cast is a
no-op on current main and was dropped too.

Still to come, each as its own small PR, authored by @kings1ay3r with his
sign-off preserved:

  • DateParser early return, using @blackakula's isNaN suggestion
  • conformance steps.ts guard on the time comparison
  • trailing whitespace in validation.ts

The last of those will carry Closes #646.

Credit

The fix is @kings1ay3r's idea from #646, reshaped to the smallest change
and given tests and docs. Recorded as Co-authored-by on the commit.

The current implementation copies binary object and values into the header map as-is. This would produce incorrectly encoded HTTP headers, and the current behaviour relies on object coercion in axios. Instead we are explicitly serializing incompatible types to String.

The CloudEvents type system has no object type, so accepting objects as
extension values is an SDK convenience rather than spec behavior. In the long run, we should pivot to a spec compliant option, and throw an error for this case,  and instead as the client to correctly set headers.

Split from cloudevents#646.

Signed-off-by: Shiva <shiv@shiv.me>
Co-authored-by: Kevin Mathew Sunny <tokevins@protonmail.com>
@shiva shiva closed this Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant