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:

  • any
  • array
  • configuration
  • integer
  • log
  • hash
  • real
  • service
  • string

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.