Skip to content

Errors

Every refusal is a distinct sentinel, created with errors.NewSentinel, so it survives a process boundary and can be counted by kind rather than as "parse failed". Match with errors.Is. Their codes start envelope.; each format's own refusals keep their module's prefix.

Validation

ErrMissingAttribute

One of the four required attributes — id, source, specversion, type — is absent or empty. Returned by Validate, and so by every format's Encode and Decode: a producer learns in its own process that it would emit something no consumer accepts.

Also returned for an extension present with an empty value. An extension is caller-defined, so present-with-nothing is a state the caller chose and one binary mode cannot represent — an empty value writes no header, and the extension would vanish in transit with no error.

ErrUnsupportedSpecVersion

specversion is present and is not 1.0. An absent specversion is ErrMissingAttribute, and the distinction is load bearing: "this is not a CloudEvent at all" and "this is a CloudEvent from a version we do not implement" are different problems, and an operator alerting on version skew wants only the second.

This is the refusal the rest of the strictness follows from: specversion tells a reader how to interpret every other attribute, so continuing past an unrecognised one is guessing at the shape of what is held rather than being lenient about it.

If you see this in production, something is emitting a CloudEvents 0.3 event.

ErrInvalidTime

time arrived and is not an RFC 3339 timestamp. Absent is fine; unparseable is not.

ErrDataSchemaNotAbsolute

dataschema is present and is not an absolute URI. The specification types it as URI rather than URI-reference, so unlike source there is no base to resolve a relative one against, and a consumer receiving one could not tell which schema was meant.

ErrInvalidExtensionName

An extension name breaks the specification's naming MUSTs. Names must be one or more lower-case ASCII letters or digits, and not data. The specification's SHOULDs, starting with a letter and staying under twenty characters, are deliberately not enforced: refusing a name the rest of the world accepts is the interoperability failure that implementing a standard exists to avoid.

ErrReservedExtensionName

An extension is named after one of the eight specification attributes — id, source, specversion, type, datacontenttype, dataschema, subject, time.

Separate from ErrInvalidExtensionName because the answer is different: the name is well formed, it is just already taken. Without the refusal, Extensions["id"] would overwrite the id on the way out in binary mode, and the event would travel with a hijacked identity, no error, and a round trip that agrees with itself.

ErrControlCharacter

An attribute value contains a character the specification's String type disallows — U+0000–U+001F or U+007F–U+009F.

A format's encoding might carry them, which is precisely why they are refused instead. net/http rewrites a newline in a header rather than refusing it, so without the check a receiver would get a different attribute value than the sender set, with nothing erroring anywhere.

ErrInvalidEncoding

An attribute or extension value is not valid UTF-8, or a format's own encoding of one does not decode, such as a malformed percent-escape in binary mode. Decoding it leniently would produce a different string than the sender wrote, silently.

Reading and formats

ErrUnrecognised

Read was given a message no format claims. It is not malformed: it is in no format this reader was given. If it should have been, the reader's chain is missing a format.

ErrAmbiguous

Read was given a message two or more formats claim, such as a CloudEvents structured-mode message carrying a stray ce-specversion header. Decoding it with either could produce the wrong event, so it is decoded with neither, and the error names the formats that claimed it.

ErrUnrepresentable

A format was asked to encode an event that is valid but that its envelope cannot carry, such as an ID that is not a UUID in an envelope that requires one. CloudEvents' own formats never return it; an interop format's may, and each format's documentation lists what it refuses.

CloudEvents formats

Returned by gitlab.com/phpboyscout/go/envelope-cloudevents, and only by it. Their codes keep the module's prefix, cloudevents., so a counter by code tells the formats apart.

ErrAmbiguousHeader

One attribute arrived twice with different values, as two spellings that fold to the same key, or as two values under one key. A Wire.ContentType that differs from a content-type header counts. In structured mode, a JSON member that appears twice is refused whatever its values, and so is a body carrying both data and data_base64.

Only the binding's prefixed headers and content-type are considered. X-Forwarded-For with two hops is the ordinary shape behind a proxy and is none of the format's business: it is returned in Foreign.Headers.

Like every other refusal, this one returns the partial event: the ambiguous attribute is untrustworthy, everything else that was readable is not.

ErrReadsAsStructured

Binary(b).Encode was given an event whose datacontenttype starts application/cloudevents, the prefix every binding reserves for structured mode. A reader accepting both modes would refuse it as ErrAmbiguous, so the publisher hears instead. Publish it with Structured(), or give it a content type such as application/json.

ErrInvalidJSON

A structured-mode body is not one JSON object of CloudEvents members: not an object at all, data after it, an attribute that is not a string, an extension that is an object or array, or a data_base64 that is not base64.

What a refusal returns alongside the error

Every format's Decode returns the partially-assembled Event, carrying whatever was readable, including its extensions, and so does Read once a format has claimed the message. ErrUnrecognised and ErrAmbiguous return an empty event, because nothing was decoded. Log e.ID and e.Source rather than "a message failed to parse": core NATS has no dead letter, so a refused message is gone unless something records which one it was.