Skip to content

Read and write more than one format

This is for code that sits between an event and a transport, such as a messaging adapter. A service that only ever uses binary mode over NATS or HTTP can call Encode and Decode on cloudevents.Binary(...) directly, as in Send a CloudEvent over NATS.

Choose a writer

Binary mode is the one to prefer, because anything routing on an attribute can see it in a header. Pick the binding for the transport:

var writer envelope.Format = cloudevents.Binary(cloudevents.NATS) // or HTTP, Kafka, AMQP

Use structured mode when the transport limits its headers. SQS carries ten message attributes, and an event with every optional attribute set and three extensions needs eleven in binary mode. SNS also refuses a body that is not UTF-8, which rules out a protobuf payload:

writer = cloudevents.Structured()

Publish

Encode returns an envelope.Wire. Put ContentType wherever the transport keeps its content type: a native property on AMQP and RabbitMQ, the content-type header on NATS and SQS.

w, err := writer.Encode(e)
if err != nil {
    return err
}

header := nats.Header(w.Headers)
if header == nil {
    header = nats.Header{}
}

if w.ContentType != "" {
    header["content-type"] = []string{w.ContentType}
}

err = client.PublishMsg(ctx, &nats.Msg{Subject: "orders.created", Header: header, Data: w.Body})

Read whatever arrives

Give Read every format the reader should accept. A reader that accepts both modes can be deployed before any writer changes, which is what makes the change of mode safe:

readers := []envelope.Format{cloudevents.Binary(cloudevents.NATS), cloudevents.Structured()}

e, foreign, format, err := envelope.Read(
    envelope.Wire{Headers: msg.Header, Body: msg.Data},
    readers...,
)
if err != nil {
    refused.Add(1)
    logger.Error("refused a CloudEvent", "id", e.ID, "err", err)

    return
}

Read returns three things beside the event:

  • foreign is everything on the wire that is not part of the event, such as Nats-Msg-Id. Read it if you need it; do not send it on.
  • format is the one that decoded the message. Write a reply in it, so a structured-mode caller gets a structured-mode answer.
  • err is envelope.ErrUnrecognised when no format claims the message and envelope.ErrAmbiguous when two do. Neither decodes, so e is empty for both. Every other refusal carries the partial event, as Decode's do.

Do not order the formats to break a tie

It is tempting to treat the list as a priority order and take the first format that claims a message. No order works: a structured-mode message carrying a stray ce-specversion header is misread as malformed binary when binary goes first, and a binary event whose payload is itself a structured CloudEvent is misread as the inner event when structured goes first. That is why Read refuses a message two formats claim, rather than choosing.