Constraints

Overview

Xeto includes a built-in set of constraint rules that are used for data validation.

Number Constraints

Number specs can be annotated with the following meta to constrain their value space:

  • minVal: inclusive minimum value
  • maxVal: inclusive maximum value
  • unit: requires the number to have a specific unit
  • quantity: requires the number to have a unit of a specific quantity

Examples:

Foo: {
  percent: Number <minVal: 0, maxVal: 100, unit:"%">
  powerVal: Number <quantity:"power">
}

// Slot 'percent': Number 50°C must have unit of '%'
@a: Foo { percent:50°C, powerVal:70kW }

// Slot 'percent': Number 200% > maxVal 100
@b: Foo { percent:200%, powerVal:70kW }

// Slot 'power': Number must be 'power' unit; '°C' has quantity of 'temperature'
@c: Foo { percent:50%, powerVal:70°C }

Str Constraints

Str specs can be annotated with the following meta to constrain their value space:

  • minVal: inclusive minimum length
  • maxVal: inclusive maximum length
  • nonEmpty: marker tag to require string to have non-whitespace characters
  • pattern: regex for required string pattern (example further below)
Foo: {
  name: Str <nonEmpty>
  phone: Str <minVal: 7, maxVal:10>
}

// Slot 'name': String must be non-empty
@a: Foo { name:"", phone:"5551234" }

//  Slot 'phoneNum': String size 3 < minSize 7
@b: Foo { name:"Bob", phone:"555" }

// Slot 'phoneNum': String size 11 > maxSize 10
@c: Foo { name:"Bob", phone:"21255512345" }

List Constraints

List specs can be annotated with the following meta to constrain their value space:

  • minSize: inclusive minimum length
  • maxSize: inclusive maximum length
  • nonEmpty: marker tag to require at least one item
Foo: {
  listA: List <nonEmpty>
  listB: List <minSize:2, maxSize:3>
}

// Slot 'listA': List must be non-empty
@a: Foo { listA:{}, listB:{"a", "b"}}

// Slot 'listB': List size 1 < minSize 2
@b: Foo { listA:{"x"}, listB:{"a"}}

// Slot 'listB': List size 4 > maxSize 3
@c: Foo { listA:{"x"}, listB:{"a", "b", "c", "d"}}

Scalar Constraints

All scalars must support a string encoding. The string encoding can be constrained at compile time using the pattern tag with a regular expression:

SocialSecurityNumber: Scalar <pattern:"\\d{3}-\\d{2}-\\d{4}">

// ok
@a: {ssn: SocialSecurityNumber "123-43-5678"}

// String encoding does not match pattern for 'acme::SocialSecurityNumber'
@b: {ssn: SocialSecurityNumber "123435678"}

Invariant Values

A spec can define a fixed value for a slot via the invariant marker. This requires all instances to have that exact value as defined by the slot spec:

Foo: {
  // requires all instances to use the invariant value "%"
  unit: Unit <invariant> "%"
}

// Slot 'unit': Must have invariant value '%'
@a: Foo {unit:"meter"}

Query Constraints

Query slots may declare constraints used to validate an instance's reference graph. The canonical example is the required points of an equip:

StandardVav: Equip {
  points: {
    temp: ZoneAirTempSensor
    co2: ZoneCo2Sensor?
  }
}

Query constraints validated by computing the queries extent. In the example above the points contained by the equip. Then each constraint must match exactly one entity in the extent:

  • zero matches is an error unless the constraint is a maybe type
  • multiple matches is always an error, even for a maybe type, because the match is ambiguous

Constraints may be declared as named types or as inline as anonymous tags:

DTemp: {discharge, temp}
DFlow: {discharge, flow}

Ahu1: Equip {
  points: {
    // named shape types; auto-named constraints report by type qname
    DTemp
    DFlow

    // inline anonymous marker sets
    Point {run, cmd}
  }
}

An extent entity matches a constraint using the same rules as sugar matching: the constraint's nominal type plus its constraint tags.

An entity whose spec tag names a constraint slot itself (such as acme::Vav.points.zoneTemp), or a subtype's override of it, is anchored to that constraint and matches it alone. Constraints with no anchored entity fall back to matching the unanchored entities by type and tags. So a template may declare constraints which differ only by name, such as vendor configuration points, and instances created from it still match exactly one each.

Queries make specs suitable as named validation profiles: a shareable, versioned spec describing the required shape of an installation - for example the required points of an ASHRAE Guideline 36 VAVs.

Haxall 4.0.7 ∙ 27-Oct-2026 09:10 EDT