Skip to main content

Typed model

Inside Andromeda, an NGSI-LD value is never a bare string or a loose JSON tree. An IRI, a URN, a language tag, a Scope, a date-time and every specification token has a type of its own, and a value of that type can only be made from valid input. That is why a request is either accepted whole or refused with the member that was wrong, and why the same input is judged the same way wherever it appears.

Values as types​

A function that takes an entity type can only be handed an entity type, never an arbitrary string, so there is no branch where an invalid one slips through. The same goes for the identifiers, attribute names, language tags, Scopes, units and the tokens of the specification such as Property or observedAt. Checking happens once, when the value is made, so no later step can forget to check.

For you, this shows up as consistency. observedAt with a numeric offset is refused on a create, an update, a batch, a temporal write and a query parameter alike, because every one of them goes through the one date-time type. Values describes what each type accepts and how it is served back.

Bodies are decoded, not inspected​

Andromeda doesn't parse a body into a general JSON tree and then look around in it. It decodes the bytes directly into the typed model of the operation, namely an entity, a subscription, a registration, a query. Anything the model can't hold stops the decode at that member, and the error points at it.

That has a few visible effects.

  • A JSON null, a member named twice or a value of the wrong kind is refused, and the detail names the member.
  • Nothing is half-accepted. A refused request has changed nothing, and a batch judges each entity on its own.
  • A query parameter the specification doesn't define is refused with InvalidRequest, so a misspelt parameter doesn't quietly do nothing.

Strictness lists the rules that follow from this.

Three forms of JSON​

Production code holds JSON in exactly three forms.

  • a typed model, for everything the broker interprets
  • the bytes it is decoded from or written to
  • a validated JSON literal, for the JSON the specification carries as opaque data, such as the value of a JsonProperty, a JSON-LD @json value, or a GeoJSON foreign member

A general JSON tree is none of these. It allocates per node, loses member order and can be made arbitrarily deep, so a deep document becomes a risk to the process. An automated check refuses any production code that builds one, with no exceptions allowed. The JSON-LD processor, jsonld, keeps its own tree, a jstrict value, and that tree is permitted only in the two places that hand JSON to and from it.

So a JsonProperty you write comes back as you wrote it, and a structured Property value is held as data with a type, never as a document the broker reshapes.

One reading of each rule​

Some rules read text, and two readings of the same text are a source of disagreement. Andromeda gives each such rule one owner, and a check keeps it that way.

  • Date-times, dates and times have one reader and one writer. The reader accepts exactly the forms the specification names, and the writer spells every instant the broker sends. A test that parses a time some other way fails the check too.
  • The Scope grammar has one module.
  • Linked entity retrieval has one engine.
  • IRI and media-type constants in the code must be validated values. For an IRI that is the compile-time iri! macro of iri-rs. A raw string constant fails the check.

One endpoint can't accept what another refuses.

What types rule out​

A broker that kept JSON as JSON could pass through members it doesn't understand, or accept a timestamp with an offset and convert it. Andromeda can't do either without changing a type, and that is the point. Anything it accepts, it can explain and give back the same way every time.