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();
export const schema = 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';
}
})
/* equivalent in declarative form; less obvious, but allows schema serialization
.unionDiscriminator([
{$get: 'content-type'},
{$first: [
{$when: [{$matches: /.+\/.*json/}, 'json']},
{$when: [{$matches: /text\/.+/}, 'text']},
{$when: ['$defined', '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'))
)
);
const content1 = {'content-type': 'text/plain', content: 'hello' };
console.log( await schema.process( content1 ) );
const content2 = {'content-type': 'application/foo+json', content: '{"stuff":123}'}
console.log( await schema.process( content2 ))
const content3 = {'content-type': 'application/octet-stream', 'content': 'SGVsbG8sIFdvcmxkIQ=='}
console.log( await schema.process( content3))
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.
See the Union guide and the examples in the project repo for more details.