shape

How to validate arrays and tuples#

Goal: validate lists whose elements share a shape, fixed-length tuples, and tuples with a variable tail.

A list of one shape#

A single-element array means "zero or more elements, each matching this shape".

TS

Shape([Number])            // any-length array of numbers
Shape({ tags: [String] })  // property that is an array of strings

Go

shape.MustShape([]any{shape.Number})
const shape = Shape([Number])
shape([1, 2, 3])  // OK
shape([1, 'x'])   // throws: index 1 is not of type number

Element error paths use the index, for example tags.1.

A fixed-length tuple#

Two or more example elements form a tuple of exactly that length; each position has its own shape.

TS

const point = Shape([Number, Number])
point([10, 20])       // OK
point([10, 20, 30])   // throws: index 2 is not allowed
point([10, 'x'])      // throws: index 1 is not of type number

Missing tuple positions are filled from their per-position defaults.

A tuple with a variable tail: Rest#

Rest(shape, tuple) matches any number of trailing elements after the fixed tuple positions. Pass the tuple as the second argument:

const { Shape, Rest } = require('shape')
// [string, number, ...number]
const shape = Shape(Rest(Number, [String, Number]))
shape(['a', 1, 2, 3])   // OK: 'a', 1, then any number of numbers
shape(['a', 1, 'x'])    // throws: index 2 is not of type number

Without a tuple, Rest(Number) is a tail only—the same as [Number]. The rest replaces a plain element shape, so Rest(Number, [String]) is an array of numbers; to keep the String as a first position, close it first: Rest(Number, Closed([String])).

Go

s := shape.MustShape(shape.Rest(shape.Number, []any{shape.String, shape.Number}))

Force a single-shape array to be closed#

[Number] is open-ended. To require exactly one element, close it:

Shape(Closed([Number]))   // exactly one number

See also#