Skip to content
Tony Pujals edited this page Jul 22, 2015 · 4 revisions

Phase Schema

Overview

This document provides the Phase schema specification.

Phase provides a simple, intuitive, easy to write and easy to process schema language for declaring type information that can be transformed into formal JSON Schema to describe and validate JSON data.

For more background, see the following:

  • [JSON Overview](JSON Overview)
  • [JSON Schema Overview](JSON Schema Overview)
  • [Phase Overview](Phase Overview)

Definitions

Instance

An instance is any JSON value. It may be described by one or more Phase schemas. An instance may also be referred to as a "Phase instance" or "Phase data".

A Phase schema can be used to validate an instance. A valid instance is a JSON value that satisfies the criteria and constraints specified by a Phase schema.

Empty Schema

An empty schema is an empty document (zero or more whitespace characters) or a document with a single, empty compound type declaration.

{}

An empty schema matches any instance.

Phase Schema

A Phase schema consists of one of the following at the root of a document.

  • An anonymous declaration
  • A declaration

Anonymous declaration

An anonymous declaration is only valid at the root of a Phase schema. It declares a type to be used to validate data. The type is either a simple annotated type or a complex type with nested property declarations.

An example of an anonymous declaration with an annotated type:

number @minimum(21) @format('integer')

An example of an anonymous declaration with a compound type containing nested property declarations.

{
  name string @required
  age number @minimum(21) @format('integer')
}

Declaration

A declaration is a name followed by an annotated type.

An example of a declaration:

age number @minimum(21) @format('integer')

Another example of a declaration:

customer {
  name string @required
  age number @minimum(18) @format('integer')
}

Nested Schemas

Schemas can be nested.

customer {
  name {
    first string @required
    last string
  } @required
  birthday string @format('yyyy-mm-dd') @minimumAge(18) @required
  address {
    street string
    city string
    state string @upper @length(2)
    zip number @format('#####', '#####-####') @required
  }
}

Names

Names are used to declare property keys.

A name must be a valid identifier according to JavaScript. In other words, an identifier is a string of Unicode characters that begin with an uppercase or lowercase letter, underscore (_), or dollar sign ($), followed by a sequence of zero or more uppercase and lowercase letters, underscores, dollar signs, and digits (0-9).

name -> id
id -> [a-zA-Z_$] [a-zA-Z0-9_$]*

For example, the following Phase schema declares two property names, name and age.

{
  name string
  age integer
}

This schema is matched by the following two JSON instances:

{
  "name": "Sally"
  "age": 28
}

and

{
  "name": "Bob"
  "age": 30
}

When a name is used as part of a root declaration, it is considered informational. For example, the following two schemas are equivalent:

{ name string; age integer }

and

person { name string; age integer }

The following instance would match either schema:

{
  "name": "Sally"
  "age": 28
}

Type Declarations

The following are valid Phase data type specifiers:

boolean
integer
number
string
array
object

The following are examples of valid type declarations:

isAwesome boolean
age integer
temperature number
name string
attendees array
something object

Array Type Declarations

An array type declaration can also used brackets notation instead of 'array':

attendees []

An array can also specify constraints on elements. The following example declares that a valid list property must be an array with three elements (a string, followed by two numbers):

list [ string, number, number ]

This defines a list property that is an array with at least one element, which must be a string, followed by any number of integer elements:

list [ string, integer... ]

The previous example is different from the following, which specifies that a list is an array in which the first element must be a string, the second must be an integer, followed by any number of elements of any type:

list [ string, integer, ... ]

The following declaration provides further constraints using an annotation to specify the array must have exactly four elements:

list [ string, integer... ] @length(4)

The following declares an list property in which the first element can be either a string or integer, followed by any number of integers; the list must have at least one element and no more than four:

list [ string | integer, integer... ] @minLength(1) @maxLength(4)

The previous is equivalent to the following:

list [ string | integer, integer, integer, integer ]

Object Type Declarations

An object type declaration can also used curly braces notation instead of 'object'.

The following example declares that an address property must be an object:

{
  address {}
}

In Phase, an object declaration is called a compound declaration, because the curly braces can contain Phase declarations that specify valid properties for instances.

For example, the following declaration specifies the properties of an address object:

person {
  address {
    street string
    city string
    state string @upper @length(2)
    zip number @format('#####', '#####-####') @required
  }
}

Annotated Type

An annotated type is a type followed by a sequence of zero or more annotations. All type declarations in Phase are annotated types.

Annotations provide the means to decorate types with auxiliary information, validation constraints, and formatting specifications.

An example of simple annotation is @required, which can be applied to any type.

An example of an annotation that requires an argument is @pattern, which can be applied to the string type. It takes a regular expression for validating string instance values.

Phase comes with an number of standard annotations that correlate directly to JSON Schema validation keywords.

Annotations for any type declaration

@required
@required

The @required annotation can be used with any declaration of any type in a compound type, including declarations of compound types.

An object instance is valid if property set contains all properties with values of the specified types declared with the @required annotation.

Example:

{
  name string @required
}

is matched by all of the following instances:

{
  "name": "Bob"
}

{
  "name": "Bob",
  "age": 30
}

{
  "name": "Bob",
  "age": 30,
  "email": "[email protected]"
}

Example:

{
  name string @required
  address {
    street string
    city string
    state string @pattern('^[A-Z]{2}$') @required
    zip string @pattern('(^[0-9]{5}$)|(^[0-9]{5}-[0-9]{4}$)') @required
  } @required
}

is matched by the following:

{
  "name": "Bob",
  "address": {
    "state": "CA"
    "zip": "94041"
  }
}

but fails with the following (missing zip)

{
  "name": "Bob",
  "address": {
    "state": "CA"
  }
}

and also fails with the following (missing address)

{
  "name": "Bob"
}
@enum

The argument for @enum must be an array with at least one element. Elements in the array must be unique and may be of any type, including null.

An instance is valid if its value is equal to one of the elements of the array declared by @enum.

Note: the @enum annotation treats an argument list as an array, so array bracket notation is optional.

Examples:

// traffic light colors
color string @enum( 'red', 'yellow', 'blue' )

 // valid U.S. currency bill denominations, enumerated with optional array notation
denominations number @enum( [ 1, 2, 5, 10, 20, 50, 100 ] )
@allOf

The argument for @allOf must be an array of one or more subschemas.

@anyOf

The argument for @anyOf must be an array of one or more subschemas.

@oneOf

The argument for @oneOf must be an array of one or more subschemas.

@not

The argument for @oneOf must be a subschema.

Numeric type annotations

@multipleOf
@multipleOf(number > 0)

The argument for @multipleOf must be a number greater than 0. A numeric instance is valid against multipleOf if the result of the division of the instance by this annotation's value is an integer.

Example:

donuts integer @multipleOf(12)
@maximum and @exclusiveMaximum
@maximum(number) [ @exclusiveMaximum(boolean) ]

The argument for @maximum must be a number. The argument for @exclusiveMaximum must be a boolean. @exclusiveMaximum is optional, but if it is present, @maximum must also be present.

Instance validation depends on the presence of @exclusiveMaximum:

  • If @exclusiveMaximum is present and evaluates to boolean value true, then an instance is valid if its value is lower than, or equal to, the value of @maximum;
  • otherwise, an instance is valid if its value is strictly lower than @maximum.

Examples:

qty integer @maximum(5)                           // qty must be less than or equal to 5
qty integer @maximum(5) @exclusiveMaximum(false)  // qty must be less than or equal to 5

qty integer @maximum(5) @exclusiveMaximum(true)   // qty must be less than 5
@minimum and @exclusiveMinimum
@minimum(number) [ @exclusiveMinimum(boolean) ]

The argument for @minimum must be a number. The argument for @exclusiveMinimum must be a boolean. @exclusiveMinimum is optional, but if it is present, @minimum must also be present.

Instance validation depends on the presence of @exclusiveMinimum:

  • If @exclusiveMinimum is present and evaluates to boolean value true, then an instance is valid if its value is greater than, or equal to, the value of @minimum;
  • otherwise, an instance is valid if its value is strictly greater than @minimum.

Examples:

qty integer @minimum(5)                           // qty must be greater than or equal to 5
qty integer @minimum(5) @exclusiveMinimum(false)  // qty must be greater than or equal to 5

qty integer @minimum(5) @exclusiveMinimum(true)   // qty must be greater than 5

String type annotations

@maxLength
@maxLength(number >= 0)

The argument for @maxLength must be an integer greater than, or equal to, 0.

A string instance is valid if its length is less than, or equal to, the value of @maxLength. The length of a string instance is defined as the number of its characters as defined by RFC 4627.

Example:

username string @maxLength(10)
@minLength
@minLength(number >= 0)

The argument for @minLength must be an integer greater than, or equal to, 0.

A string instance is valid if its length is greater than, or equal to, the value of @minLength. The length of a string instance is defined as the number of its characters as defined by RFC 4627.

@minLength, if absent, may be considered as being present with an integer value 0.

Example:

username string @minLength(3)
@pattern
@pattern(string)

The argument for @pattern must be a string, and it should be a valid regular expression, according to ECMA 262 regular expression dialect.

A string instance is considered valid if the regular expression matches the instance successfully.

Example:

username string @pattern('^[a-zA-Z]+[a-zA-Z0-9-_]*$')

Array type annotations

@additionalItems
@additionalItems(boolean)

The argument for @additionalItems must be boolean value.

An array instance is always valid if the @additionalItems annotation is not present. If present with a false value, then an array instance is only valid if its size is less than, or equal to, the size of the declared array type.

@additionalItems, if absent, may be considered as being present with a boolean value true.

@maxItems
@maxItems(integer >= 0)

The argument for @maxItems must be an integer greater than, or equal to, 0.

An array instance is valid if its size is less than, or equal to, the value of @maxItems.

@minItems
@minItems(integer >= 0)

The argument for @minItems must be an integer greater than, or equal to, 0.

An array instance is valid if its size is greater than, or equal to, the value of @maxItems.

@minLength, if absent, may be considered as being present with an integer value 0.

@uniqueItems
@uniqueItems(boolean)

The argument for @uniqueItems must be a boolean value.

If @uniqueItems is present and its value is true, then an array instance is only valid if all of its elements are unique.

@uniqueItems, if absent, may be considered as being present with a boolean value false.

Compound type annotations

A compound type declares the properties that must be matched by valid object instances. Like any type, compound types can be decorated with a sequence of zero or more annotations.

This is an example of an anonymous root declaration of a compound type with annotations:

{
  x number
  y number
} @minProperties(2) @maxProperties(2) @patternProperties({ "[xy]" number })

Here is another example with a nested compound type with annotations:

{
  part number
  codes {
    p1 number
  } @patternProperties({ p number })
}
@maxProperties
@maxProperties(number >= 0)

The argument for @maxProperties must be an integer greater than, or equal to 0.

An object instance is valid if the number of its properties is less than, or equal to, the value of @maxProperties.

@minProperties
@minProperties(number >= 0)

The argument for @minProperties must be an integer greater than, or equal to 0.

An object instance is valid if the number of its properties is greater than, or equal to, the value of @maxProperties.

@minProperties, if absent, may be considered as being present with a boolean value 0.

@additionalProperties
@patternProperties
{
  p1 boolean
} @patternProperties({
    p boolean
  })

Here is another example with a nested compound type with annotations:

{
  part number
  codes {
    p1 number
  } @patternProperties({ p number })
}