aontu

Constrain every element of a list

Type every element of a list with a `&:` spread, and know why a bare `[string]` does not.

Rendered from docs/how-to/constrain-list-elements.md in the engine repository, where a correction belongs, and where the test suite executes every example on this page.

A bare [string] is a positional constraint: it types element 0 and says nothing about the rest. Most lists are collections, and most authors learn the difference from data that should have failed:

tags: [string]
tags: [core 7]
{
  "tags": [
    "core",
    7
  ]
}

The 7 passed because position 1 was never constrained: the two lists unified element by element, and the schema list simply ran out.

The collection form is a &: spread: a template unified into every element. Write this as tags.aon:

tags: [&: string]
tags: [core 7]

Now run it:

$ aontu tags.aon
[aontu/no_scalar_unify]: Cannot unify values at path $.tags.1
...
$ echo $?
1

The path names the exact element, so a thousand-entry list fails at $.tags.999, not “somewhere in tags”.

Reach for the positional form when the positions genuinely differ (a pair, a fixed header) and for [&: T] whenever the list is a collection. close on the enclosing map seals that map’s keys and leaves the list tail open: the spread is what constrains the elements. Constraints on the list itself (length, unique) sit beside the spread with &; see length semantics.

The constructs are specified in the language reference under Lists and Spreads &:. To close the map around the list too, see forbid unexpected keys; a bare-kind element template also crosses to JSON Schema as items: see export JSON Schema.