in compiler/src/steps/read-definition-validation.ts [30:123]
export default async function readDefinitionValidation (model: model.Model, jsonSpec: Map<string, JsonSpec>): Promise<model.Model> {
for (const type of model.types) {
if (type.kind !== 'interface') continue
const readBehavior = type.behaviors?.find(behavior => behavior.type.name === 'OverloadOf')
if (readBehavior == null) continue
assert(Array.isArray(readBehavior.generics))
assert(readBehavior.generics[0].kind === 'instance_of')
for (const parent of model.types) {
if (parent.name.name === readBehavior.generics[0].type.name && parent.name.namespace === readBehavior.generics[0].type.namespace) {
assert(parent.kind === 'interface')
const readProperties = type.properties.map(p => p.name)
for (const property of parent.properties) {
// have we defined the same properties?
if (!readProperties.includes(property.name)) {
console.log(chalk.red`The property '${property.name}' is present in ${parent.name.namespace}.${parent.name.name} but not in ${type.name.namespace}.${type.name.name}`)
process.exit(1)
}
const readProperty = type.properties.find(p => p.name === property.name) as model.Property
if (property.type.kind === 'union_of' && property.type.items.some(contains(readProperty.type))) {
// this is allowed, as if the original property type is an union (of type and type[]),
// the overloaded property type should be either the same type or an element of the union
} else if (isOverloadOf(readProperty.type, property.type)) {
// this is allowed, as if the overloaded property has a different type,
// this type should be an overload of the original property type
} else if (!deepEqual(readProperty.type, property.type)) {
console.log(chalk.red`The property '${property.name}' present in ${parent.name.namespace}.${parent.name.name} does not have the same type in ${type.name.namespace}.${type.name.name}`)
process.exit(1)
}
// we have the same properties, so let's copy the metadata
for (const key in property) {
if (key === 'required') continue
if (readProperty[key] == null) {
readProperty[key] = property[key]
}
}
}
}
}
}
return model
function isOverloadOf (readType: model.ValueOf, type: model.ValueOf): boolean {
const readTypeInstance = unwrap(readType)
const typeInstance = unwrap(type)
if (readTypeInstance == null || typeInstance == null) return false
if (readTypeInstance.type.namespace !== '_builtins') {
const definition = model.types.find(t => t.name.namespace === readTypeInstance.type.namespace && t.name.name === readTypeInstance.type.name)
return definition?.kind === 'interface' && (definition.behaviors?.some(overloaded) ?? false)
}
return false
function overloaded (def: model.Inherits): boolean {
if (def.type.name !== 'OverloadOf') return false
assert(Array.isArray(def.generics))
return def.generics[0].kind === 'instance_of' &&
def.generics[0].type.namespace === typeInstance?.type.namespace &&
def.generics[0].type.name === typeInstance?.type.name
}
function unwrap (type: model.ValueOf): model.InstanceOf | null {
switch (type.kind) {
case 'instance_of':
return type
case 'array_of':
return unwrap(type.value)
default:
return null
}
}
}
function contains (readType: model.ValueOf) {
return function (type: model.ValueOf, index: number, arr: model.ValueOf[]): boolean {
if (type.kind === 'array_of') {
return deepEqual(type.value, readType)
}
return deepEqual(type, readType)
}
}
function deepEqual (a: any, b: any): boolean {
try {
assert.deepStrictEqual(a, b)
return true
} catch (err) {
return false
}
}
}