-
Notifications
You must be signed in to change notification settings - Fork 93
/
Copy pathread-definition-validation.ts
123 lines (111 loc) · 5.05 KB
/
read-definition-validation.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import assert from 'assert'
import * as model from '../model/metamodel'
import { JsonSpec } from '../model/json-spec'
import chalk from 'chalk'
/**
* Verifies if "read" version of interface definitions
* contains the same properties of their "write" version.
* Then, it copies every model.Property from write to read but 'required'.
*/
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
}
}
}