Skip to content

Why Unmarshal refuses

Unmarshal validates and refuses. A missing required attribute, an unrecognised specversion, a malformed time, a relative dataschema, an extension name breaking the naming rules: all of them are errors, not warnings.

The alternative — parse what you recognise, hand back what you got, let the consumer decide — is the more obviously friendly design, and it is wrong here for two reasons.

specversion makes leniency incoherent

specversion tells a reader how to interpret every other attribute. Not recognising it means not knowing the shape of what you are holding.

So "parse the attributes I recognise" is not tolerance. It is asserting that an attribute called ce-subject in an unknown version means what ce-subject means in 1.0, which is a guess dressed as robustness. And once that argument is accepted for specversion it applies to the rest: a malformed event is one whose meaning is not determined, and processing it anyway is choosing a meaning.

One decision, or five consumers with four answers

Pushing the choice to the caller sounds like flexibility. In practice it means every subscriber in the estate writes its own leniency rules, they diverge, and a message that one service accepts another silently interprets differently. That is the failure that is hardest to find, because nothing errors.

Deciding once, in the codec, means every consumer refuses the same thing on the same day.

The half that would have been dropped

Refusing is only the first half. Unmarshal returns the partially-parsed event alongside the error.

In a NATS subscriber, an Unmarshal that errors means the handler returns and the message is gone — core NATS has no dead letter. If all the consumer can log is "a message failed to parse", the event is unidentifiable and nobody can go and look at the producer.

Handing back the id and source of the thing that was rejected is what turns a silent drop into a traceable one. It is the same argument as go/nats's shed reporting, one layer up: the failure is acceptable, the invisibility is not.

The cost, stated

One producer emitting a malformed attribute becomes every consumer's problem at once. In an estate that controls both ends of every connection that is a fast, loud failure, and probably right.

The condition that reverses this

If this codec is ever pointed at events produced outside the estate, revisit it.

There, leniency stops being sloppiness and becomes a requirement, and strictness becomes a denial of service that anybody who can send you a message can cause. The decision is right for the traffic it was designed for and not obviously right for any other.