“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.
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.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:
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.
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 (
If you specify a subschema definintion (via the
definition keyword), and the same element is of any type other than
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.)
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
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
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
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.
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.