Skip to main content

Discriminators

When defining a Union Schema, a unionDiscriminator handler is used to decide which alternative union schema to use.

Discriminators may need to inspect values in the output target value, which means they need to either use prebuilt keywords like $reference to get these values, or they often need to be implemented as a function
using the full value processor signature.

If the discriminator returns undefined, it means the union schema cannot yet be resolved, and discrimination will be retried on another traversal pass.

Upon success, the discriminator should return a reference to the resolved schema, either the key of the unionSchema, or the actual CompiledSchema value. (Note: not the pre-compilation Schema!)


import { Schema, SchemaResolver } from '@versionzero/schema';
const resolver = new SchemaResolver();

const schema = await resolver.compile(
new Schema()
.unionDiscriminator(data => {
const contentType = data?.['content-type'];
if (!contentType) {
return undefined;
}
if (/.+\/.*json/.test(contentType)) {
return 'json';
}
else if (contentType.startsWith('text/')) {
return 'text'
}
else {
return 'data';
}
})
.unionSchema('text', new Schema('object')
.property('content-type', new Schema('string')
.default('text/plain')
)
.property('content', new Schema('string'))
)
.unionSchema('json', new Schema('object')
.property('content-type', new Schema('string')
.required()
.normalizer({$literal: 'application/json'})
)
.property('content', new Schema('string').validator('$json'))
)
.unionSchema('data', new Schema('object')

.property('content-type', new Schema('string').required())
.property('content', new Schema('string').validator('$base64'))
)
);

console.log( await schema.process( {'content-type': 'text/plain', content: 'hello' }) );
console.log( await schema.process( {'content-type': 'application/foo+json', content: '{"stuff":123}'}))
console.log( await schema.process( {'content-type': 'application/octet-stream', 'content': 'SGVsbG8sIFdvcmxkIQ=='}))

Automatic Discrimination

It isn't always necessary to provide a unionDiscriminator handler if the compiler determines that one can be automatically synthesized:

  • If the union schemas all contain unique properties.
  • If the union schemas contain common properties that are constrained to unique values.

In these cases, the discriminator will be created during compilation, and the referenced properties will be "hoisted" to the containing schema in order to make them available for assignment before resolution.

Alternatives

Unions are

See the Union guide and the examples in the project repo for more details.