12. Schemas
“Schemas” are the mechanism by which you can restrict what values are contributed to configuration points, or which parameters are acceptable to a service constructed via a factory service.
12.1. Basic Format
In YAML terminology, a schema is simply a map that follows a special format. Consider the following configuration point:
MyConfigurationPoint:
type: map
schema:
definition:
user.name:
type: string
required: true
home.directory:
type: string
user.groups:
type: array
This configuration point is a map that only allows three keys: user.name, home.directory, and user.groups. Any contribution made to this configuration point must not contain any keys other than these values. And since the user.name key is marked as required, any contribution to this configuration point must contain at least that key.
Note the type definitions as well. The recognized types are:
anyarrayconfigurationintegerloghashrealservicestring
If the type is left out, it defaults to any. If a value is contributed to that key that is not of the required type, a validation error results.
Note that schemas may be applied to service points as well, in identical fashion to that of configuration points. (That is to say, they are introduced by the schema descriptor element.) In that case, the schema applies to the parameters of any service point that uses this service point as its factory service.
12.2. Subschemas
Schemas may be nested, to allow for arbitrarily deep schema “trees”. Consider the following example:
MyConfigurationPoint:
type: map
schema:
definition:
user.name:
type: string
required: true
home.directory:
type: string
user.groups:
type: array
user.address:
definition:
line1:
type: string
line2:
type: string
city:
type: string
state:
type: string
zip:
type: string
In this case, the user.address element of the schema is itself another schema. That is to say, any element that it matches must be a hash, which may be empty, but which may contain no keys other than those specified (line1, line2, city, state, and zip).
If you specify a subschema definintion (via the definition keyword), and the same element is of any type other than hash or array, you’ll get an error. You cannot used schema’s to validate any other type of data. (Arrays are a special case—see the next section.)
12.3. Arrays
When you specify a schema for an array (or for a configuration point of type list), the schema will be applied to every element of any array that is contributed. For example, consider this:
MoviesILike:
type: list
schema:
definition:
name:
type: string
required: true
genre:
type: string
actors:
type: array
definition:
name:
type: string
required: true
gender:
type: string
birthdate:
type: string
This configuration point is a list. Every element that is contributed to it must conform to the given schema. Note, too, that the actors element of the schema expects an array, and that each element of that array must conform to the given subschema.
Given that schema, the following contribution would be valid:
contributions:
MoviesILike:
- name: Twelve Angry Men
genre: Drama
actors:
- name: Henry Fonda
gender: male
birthdate: 16 May 1905
- name: Jack Klugman
birthdate: 27 Apr 1922
- name: Ed Binns
- name: John Fiedler
- name: Lawrence of Arabia
actors:
- name: Peter O'Toole
- name: Alec Guinness
birthdate: 2 Apr 1914
- name: Remains of the Day
12.4. Named vs. Anonymous Schemas
The schemas that have been shown so far have been anonymous. This is fine for schemas that are very specific and have a very special purpose. However, sometimes, you want several configuration points of service points to have the same schema. To accomplish this, you need to give your schemas names.
Named schemas are owned by the package in which they are defined, but once named they may be used in any package (just like service points and configuration points).
To name a schema, just add a name element at the same level as the definition element:
MoviesILike:
type: list
schema:
name: MovieDefinition
definition:
...
Then, you can reuse the schema by putting the schema name after the schema element, instead of a hash:
MoviesIHate:
type: list
schema: MovieDefinition
This second configuration point will reuse the MovieDefinition schema. Note that you can specify an existing schema (or name a schema) at any level of a schema definition:
MoviesILike:
type: list
schema:
name: MovieDefinition
definition:
name:
type: string
required: true
genre:
type: string
actors:
name: ActorDefinition
type: array
definition:
name:
type: string
required: true
gender:
type: string
birthdate:
type: string
MoviesIHate:
type: list
schema: MovieDefinition
FavoriteActors:
type: list
schema: ActorDefinition
PartiesAttended:
type: list
schema:
definition:
place:
type: string
host:
type: string
attendees: ActorDefinition
12.5. Extending Schemas
Sometimes it happens that you want to create a schema that is almost like an existing schema, but adds one or two new elements. You can do this in Copland by extending the existing schema:
People:
type: list
schema:
name: PersonSchema
definition:
name:
required: true
type: string
gender:
required: true
type: string
Employees:
type: list
schema:
name: EmployeeSchema
extend: PersonSchema
definition:
department:
required: true
type: string
In the above instance, the “EmployeeSchema” is exactly like the PersonSchema, except it adds a “department” key.
12.6. Limitations
The existing schema implementation is sufficient for most purposes, but it has some limitations:
- You cannot specify a format for a non-hash value.
- You cannot specify whether a subschema that reuses an existing schema is required or not.
- You cannot enforce the constraint “any one of a set of keys is required.” The schema subsystem only understands a single key being required or not.
For most purposes, however, it is sufficient.