@lionweb/validation 0.5.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ import { IncorrectLionCoreVersion_Issue, NotLionCoreLanguageKey_Issue, NumberOfLanguagesUsed_Issue } from "../issues/LanguageIssues";
2
+ import { JsonContext } from "../issues/ValidationIssue";
3
+ import { LionWebJsonChunk } from "../json/LionWebJson";
4
+ import { LionWebJsonChunkWrapper } from "../json/LionWebJsonChunkWrapper";
5
+ import { ValidationResult } from "./ValidationResult";
6
+
7
+ export class LionWebLanguageValidator {
8
+ validationResult: ValidationResult;
9
+
10
+ constructor() {
11
+ this.validationResult = new ValidationResult();
12
+ }
13
+
14
+ /**
15
+ * check JSON as in `check`, but also check whether the metamoddel is a Language.
16
+ * @param obj
17
+ */
18
+ checkLanguage(chunk: LionWebJsonChunk) {
19
+ // TODO Vaalidate syntax first via LionWebSyntaxValidator !!!
20
+ if (chunk.languages.length !== 1) {
21
+ // Check whether the LionCore M3 is the only used language, then this is a language model.
22
+ this.validationResult.issue(new NumberOfLanguagesUsed_Issue(new JsonContext(null, ["languages"]), chunk.languages.length))
23
+ return
24
+ }
25
+ const usedLanguage = chunk.languages[0];
26
+ if (usedLanguage.key !== "LionCore-M3") {
27
+ this.validationResult.issue(new NotLionCoreLanguageKey_Issue(new JsonContext(null, ["languages[0]"]), usedLanguage.key))
28
+ }
29
+ if (usedLanguage.version !== "1") {
30
+ this.validationResult.issue(new IncorrectLionCoreVersion_Issue(new JsonContext(null, ["languages[0]"]), usedLanguage.version))
31
+ }
32
+ const chunkWrapper = new LionWebJsonChunkWrapper(chunk);
33
+ const languageNodes = chunkWrapper.findNodesOfConcept("Language");
34
+ if (languageNodes.length !== 1) {
35
+ // TODO Better error handling.
36
+ console.error("Expected exactly one Language node, found " + languageNodes.length + " => " + JSON.stringify(languageNodes));
37
+
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,213 @@
1
+ import {
2
+ Duplicates_Issue,
3
+ Reference_ChildMissingInParent_Issue,
4
+ Reference_CirculairParent_Issue,
5
+ Reference_DuplicateNodeId_Issue, Reference_LanguageUnknown_Issue,
6
+ Reference_ParentMissingInChild_Issue
7
+ } from "../issues/ReferenceIssues";
8
+ import { JsonContext } from "../issues/ValidationIssue";
9
+ import { ChunkUtils } from "../json/ChunkUtils";
10
+ import { LION_CORE_BUILTINS_KEY, LionWebJsonChild, LionWebJsonChunk, LionWebJsonMetaPointer, LionWebJsonNode, LwJsonUsedLanguage } from "../json/LionWebJson";
11
+ import { LionWebJsonChunkWrapper } from "../json/LionWebJsonChunkWrapper";
12
+ import { SimpleFieldValidator } from "./SimpleFieldValidator";
13
+ import { ValidationResult } from "./ValidationResult";
14
+
15
+ /**
16
+ * Assuming that the syntax is correct, check whether all LionWeb references are correct,
17
+ * as far as they do not need the used language definition.
18
+ */
19
+ export class LionWebReferenceValidator {
20
+ validationResult: ValidationResult;
21
+ nodesIdMap: Map<string, LionWebJsonNode> = new Map<string, LionWebJsonNode>();
22
+ simpleFieldValidator: SimpleFieldValidator;
23
+
24
+ constructor(validationResult: ValidationResult) {
25
+ this.validationResult = validationResult;
26
+ this.simpleFieldValidator = new SimpleFieldValidator(this.validationResult);
27
+ }
28
+
29
+ validateNodeIds(obj: LionWebJsonChunk, ctx: JsonContext): void {
30
+ // put all nodes in a map, validate that there are no two nodes with the same id.
31
+ obj.nodes.forEach((node, index) => {
32
+ // this.validationResult.check(this.nodesIdMap.get(node.id) === undefined, `Node number ${index} has duplicate id "${node.id}"`);
33
+ if (! (this.nodesIdMap.get(node.id) === undefined)) {
34
+ this.validationResult.issue(new Reference_DuplicateNodeId_Issue(ctx.concat("nodes", index), node.id));
35
+ }
36
+ this.nodesIdMap.set(node.id, node);
37
+ });
38
+ }
39
+
40
+ validate(obj: LionWebJsonChunkWrapper): void {
41
+ const rootCtx = new JsonContext(null, ["$"]);
42
+ this.checkDuplicateUsedLanguage(obj.jsonChunk.languages, rootCtx);
43
+ this.validateNodeIds(obj.jsonChunk, rootCtx);
44
+ obj.jsonChunk.nodes.forEach((node, nodeIndex) => {
45
+ const context = rootCtx.concat(`node`, nodeIndex);
46
+ const parentNode = node.parent;
47
+ if (parentNode !== null) {
48
+ this.validateExistsAsChild(context, this.nodesIdMap.get(parentNode), node);
49
+ }
50
+ this.validateLanguageReference(obj, node.classifier, context);
51
+ this.checkParentCircular(node, context);
52
+ this.checkDuplicate(node.annotations, rootCtx.concat("node", nodeIndex, "annotations"));
53
+ this.validateChildrenHaveCorrectParent(node, rootCtx.concat("node", nodeIndex));
54
+ node.properties.forEach((prop, propertyIndex) => {
55
+ this.validateLanguageReference(obj, prop.property, rootCtx.concat("node", nodeIndex, "property", propertyIndex));
56
+ });
57
+ node.containments.forEach((containment, childIndex) => {
58
+ this.validateLanguageReference(obj, containment.containment, rootCtx.concat("node", nodeIndex, "containments", childIndex));
59
+ this.checkDuplicate(containment.children, rootCtx.concat("node", nodeIndex, "containments", childIndex));
60
+ containment.children.forEach((childId) => {
61
+ const childNode = this.nodesIdMap.get(childId);
62
+ if (childNode !== undefined) {
63
+ if (childNode.parent !== null && childNode.parent !== undefined && childNode.parent !== node.id) {
64
+ // TODO this.validationResult.error(`Child "${childId}" with parent "${childNode.parent}" is defined as child in node "${node.id}"`);
65
+ }
66
+ if (childNode.parent === null || childNode.parent === undefined) {
67
+ // TODO this.validationResult.error(`Child "${childId}" of node "${node.id}" has different parent "${childNode.parent}"`);
68
+ }
69
+ }
70
+ });
71
+ });
72
+ node.references.forEach((ref, refIndex) => {
73
+ this.validateLanguageReference(obj, ref.reference, rootCtx.concat("node", nodeIndex, "references", refIndex));
74
+ // TODO Check for duplicate targets?
75
+ // If so, what to check because there can be either or both a `resolveInfo` and a `reference`
76
+ });
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Check whether the metapointer refers to a language defined in the usedLanguages of chunk.
82
+ * @param chunk
83
+ * @param metaPointer
84
+ * @param context
85
+ */
86
+ validateLanguageReference(chunk: LionWebJsonChunkWrapper, metaPointer: LionWebJsonMetaPointer, context: JsonContext) {
87
+ const lang = ChunkUtils.findLwUsedLanguageWithVersion(chunk.jsonChunk, metaPointer.language, metaPointer.version);
88
+ if (metaPointer.language === LION_CORE_BUILTINS_KEY) {
89
+ // Ok, builtin
90
+ return;
91
+ }
92
+ if (lang === undefined || lang === null) {
93
+ this.validationResult.issue(new Reference_LanguageUnknown_Issue(context, metaPointer))
94
+ } else {
95
+ if (lang.version !== metaPointer.version) {
96
+ this.validationResult.issue(new Reference_LanguageUnknown_Issue(context, metaPointer))
97
+ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Check whether there are duplicate values in `strings`.
103
+ * @param strings
104
+ * @param context
105
+ */
106
+ checkDuplicate(strings: string[], context: JsonContext) {
107
+ if (strings === null || strings === undefined) {
108
+ return;
109
+ }
110
+ const alreadySeen: Record<string, boolean> = {};
111
+ strings.forEach((str) => {
112
+ if (alreadySeen[str]) {
113
+ this.validationResult.issue(new Duplicates_Issue(context, str))
114
+ } else {
115
+ alreadySeen[str] = true;
116
+ }
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Checks whether there are duplicate usedLanguages in `usedLanguages`.
122
+ * usedLanguages are considered equal when bith their `key` and `version` are identical.
123
+ * @param usedLanguages
124
+ * @param context
125
+ */
126
+ checkDuplicateUsedLanguage(usedLanguages: LwJsonUsedLanguage[], context: JsonContext) {
127
+ if (usedLanguages === null || usedLanguages === undefined) {
128
+ return;
129
+ }
130
+ const alreadySeen = new Map<string, string[]>();
131
+ usedLanguages.forEach((usedLanguage, index) => {
132
+ const seenKeys = alreadySeen.get(usedLanguage.key);
133
+ if (seenKeys !== null && seenKeys !== undefined) {
134
+ if (seenKeys.includes(usedLanguage.version)) {
135
+ this.validationResult.issue(new Duplicates_Issue(context.concat("language", index, "version"), usedLanguage.version));
136
+ }
137
+ } else {
138
+ alreadySeen.set(usedLanguage.key, [usedLanguage.version]);
139
+ }
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Checks whether the parent of node recursively points to `node` itself.
145
+ * @param node
146
+ */
147
+ checkParentCircular(node: LionWebJsonNode, context: JsonContext) {
148
+ if (node === null || node === undefined) {
149
+ return;
150
+ }
151
+ if (node.parent === null || node.parent === undefined) {
152
+ return;
153
+ }
154
+ let current: LionWebJsonNode | undefined = node;
155
+ const seenParents = [node.id];
156
+ while (current !== null && current !== undefined && current.parent !== null && current.parent !== undefined) {
157
+ const nextParent = current.parent;
158
+ if (nextParent !== null && nextParent !== undefined && seenParents.includes(nextParent)) {
159
+ this.validationResult.issue(new Reference_CirculairParent_Issue(context.concat("???"), this.nodesIdMap.get(nextParent), seenParents));
160
+ return;
161
+ }
162
+ seenParents.push(nextParent);
163
+ current = this.nodesIdMap.get(nextParent);
164
+ }
165
+ }
166
+
167
+ validateExistsAsChild(context: JsonContext, parent: LionWebJsonNode | undefined, child: LionWebJsonNode) {
168
+ if (parent === undefined || parent === null) {
169
+ return;
170
+ }
171
+ for (const containment of parent.containments) {
172
+ if (containment.children.includes(child.id)) {
173
+ return;
174
+ }
175
+ }
176
+ if (parent.annotations.includes(child.id)) {
177
+ return;
178
+ }
179
+ this.validationResult.issue(new Reference_ChildMissingInParent_Issue(context, child, parent));
180
+ }
181
+
182
+ validateChildrenHaveCorrectParent(node: LionWebJsonNode, context: JsonContext) {
183
+ node.containments.forEach((child: LionWebJsonChild) => {
184
+ child.children.forEach((childId: string, index: number) => {
185
+ const childNode = this.nodesIdMap.get(childId);
186
+ if (childNode !== undefined) {
187
+ if (childNode.parent !== node.id) {
188
+ // TODO Check that this is already tested from the child in vaidateExistsAsChild().
189
+ }
190
+ if (childNode.parent === null || childNode.parent === undefined) {
191
+ this.validationResult.issue(new Reference_ParentMissingInChild_Issue(context.concat("child", "containment", "key", index), node, childNode));
192
+ }
193
+ }
194
+ });
195
+ });
196
+ node.annotations.forEach((annotationId: string, annotationIndex: number) => {
197
+ const childNode = this.nodesIdMap.get(annotationId);
198
+ if (childNode !== undefined) {
199
+ if (childNode.parent === null || childNode.parent === undefined) {
200
+ this.validationResult.issue(new Reference_ParentMissingInChild_Issue(context.concat("annotations", annotationIndex), node, childNode));
201
+ }
202
+ }
203
+ });
204
+ // for (const childId of NodeUtils.allChildren(node)) {
205
+ // const childNode = this.nodesIdMap.get(childId);
206
+ // if (childNode !== undefined) {
207
+ // if (childNode.parent !== node.id) {
208
+ // this.validationResult.error(`QQ Parent of child "${childId}" is "${childNode.parent}", but should be "${node.id}" in ${context}`);
209
+ // }
210
+ // }
211
+ // }
212
+ }
213
+ }
@@ -0,0 +1,297 @@
1
+ import {
2
+ Syntax_ArrayContainsNull_Issue,
3
+ Syntax_PropertyMissingIssue,
4
+ Syntax_PropertyNullIssue,
5
+ Syntax_PropertyTypeIssue,
6
+ Syntax_PropertyUnknownIssue
7
+ } from "../issues/SyntaxIssues";
8
+ import { SimpleFieldValidator, ValidatorFunction } from "./SimpleFieldValidator";
9
+ import { JsonContext } from "../issues/ValidationIssue";
10
+ import { ValidationResult } from "./ValidationResult";
11
+
12
+ export type UnknownObjectType = { [key: string]: unknown };
13
+
14
+ export type PropertyType =
15
+ "string"
16
+ | "number"
17
+ | "bigint"
18
+ | "boolean"
19
+ | "symbol"
20
+ | "undefined"
21
+ | "object"
22
+ | "function"
23
+ | "array";
24
+
25
+ export type PropertyDefinition = {
26
+ /**
27
+ * The property name
28
+ */
29
+ property: string;
30
+ /**
31
+ * The expected type of the property value
32
+ */
33
+ expectedType: PropertyType;
34
+ /**
35
+ * Whether the property value is allowed to be null
36
+ */
37
+ mayBeNull: boolean;
38
+ /**
39
+ * If the property type is correct, check its value further with this function.
40
+ * Will, only be called if `this.recursive === true`.
41
+ * If the property value is an Array, the `checkValue` will be called on each element in the array.
42
+ * @param obj
43
+ * @param ctx
44
+ */
45
+ validateValue?: ValidatorFunction;
46
+ };
47
+
48
+ // Make boolean argument more readable.
49
+ const MAY_BE_NULL = true;
50
+ const NOT_NULL = false;
51
+
52
+ /**
53
+ * LionWebCheck can chack whether objects are LionWeb JSON objects.
54
+ * The check can be on a single object, or recursively on an object and its children.
55
+ */
56
+ export class LionWebSyntaxValidator {
57
+ validationResult: ValidationResult;
58
+ simpleFieldValidator: SimpleFieldValidator;
59
+ /**
60
+ * When true, each function will work recursively on the given object.
61
+ * When false, will only check the given object.
62
+ * Metapointers are always checked as part of the object, disregarding the va;lue of `recursive`.
63
+ */
64
+ recursive: boolean = true;
65
+
66
+ constructor(validationResult: ValidationResult) {
67
+ this.validationResult = validationResult;
68
+ this.simpleFieldValidator = new SimpleFieldValidator(this.validationResult);
69
+ }
70
+
71
+ /**
72
+ * Check whether `obj` is a JSON object that conforms to the serialization syntax of LionCore.
73
+ * All errors found will be pushed into the `errors` array, if its length is not 0, the check has failed.
74
+ * @param obj
75
+ */
76
+ validate(obj: unknown) {
77
+ this.validateLwChunk(obj, new JsonContext(null, ["$"]));
78
+ }
79
+
80
+ validateLwChunk = (obj: unknown, ctx: JsonContext): void => {
81
+ const expected: PropertyDefinition[] = [
82
+ { property: "serializationFormatVersion", expectedType: "string", mayBeNull: NOT_NULL, validateValue: this.simpleFieldValidator.validateSerializationFormatVersion },
83
+ { property: "languages", expectedType: "array", mayBeNull: NOT_NULL, validateValue: this.validateLwUsedLanguage },
84
+ { property: "nodes", expectedType: "array", mayBeNull: NOT_NULL, validateValue: this.validateLwNode },
85
+ ];
86
+ this.propertyChecks(obj, expected, ctx);
87
+ }
88
+
89
+ validateLwUsedLanguage = (obj: unknown, ctx: JsonContext): void => {
90
+ const expected: PropertyDefinition[] = [
91
+ { property: "key", expectedType: "string", mayBeNull: NOT_NULL, validateValue: this.simpleFieldValidator.validateKey },
92
+ { property: "version", expectedType: "string", mayBeNull: NOT_NULL, validateValue: this.simpleFieldValidator.validateVersion }
93
+ ];
94
+ this.propertyChecks(obj, expected, ctx);
95
+ }
96
+
97
+ validateLwNode = (obj: unknown, ctx: JsonContext): void => {
98
+ const expected: PropertyDefinition[] = [
99
+ { property: "id", expectedType: "string", mayBeNull: NOT_NULL, validateValue: this.simpleFieldValidator.validateId },
100
+ { property: "classifier", expectedType: "object", mayBeNull: NOT_NULL, validateValue: this.validateLwMetaPointer },
101
+ { property: "properties", expectedType: "array", mayBeNull: NOT_NULL, validateValue: this.validateLwProperty },
102
+ { property: "containments", expectedType: "array", mayBeNull: NOT_NULL, validateValue: this.validateLwChild },
103
+ { property: "references", expectedType: "array", mayBeNull: NOT_NULL, validateValue: this.validateLwReference },
104
+ { property: "annotations", expectedType: "array", mayBeNull: NOT_NULL, validateValue: this.validateLwAnnotation },
105
+ { property: "parent", expectedType: "string", mayBeNull: MAY_BE_NULL, validateValue: this.simpleFieldValidator.validateId },
106
+ ];
107
+ this.propertyChecks(obj, expected, ctx);
108
+ }
109
+
110
+ validateLwAnnotation = (obj: unknown, context: JsonContext) => {
111
+ if (this.checkType(obj, "string", context)) {
112
+ this.simpleFieldValidator.validateId(obj as string, context);
113
+ }
114
+ }
115
+
116
+ validateLwProperty = (obj: unknown, ctx: JsonContext): void => {
117
+ const expected: PropertyDefinition[] = [
118
+ { property: "property", expectedType: "object", mayBeNull: NOT_NULL, validateValue: this.validateLwMetaPointer },
119
+ { property: "value", expectedType: "string", mayBeNull: MAY_BE_NULL },
120
+ ];
121
+ this.propertyChecks(obj, expected, ctx);
122
+ // TODO: hack for keys in M2 models
123
+ // if ((obj as any)["property"].key === "IKeyed-key") {
124
+ // // console.log("CHECKING KEY");
125
+ // this.simpleFieldValidator.validateKey((obj as any)["value"], ctx);
126
+ // }
127
+ }
128
+
129
+ validateLwMetaPointer = (obj: unknown, ctx: JsonContext): void => {
130
+ const expected: PropertyDefinition[] = [
131
+ { property: "key", expectedType: "string", mayBeNull: NOT_NULL, validateValue: this.simpleFieldValidator.validateKey },
132
+ { property: "version", expectedType: "string", mayBeNull: MAY_BE_NULL, validateValue: this.simpleFieldValidator.validateVersion },
133
+ { property: "language", expectedType: "string", mayBeNull: MAY_BE_NULL, validateValue: this.simpleFieldValidator.validateKey },
134
+ ];
135
+ this.propertyChecks(obj, expected, ctx);
136
+ }
137
+
138
+ validateLwChild = (obj: unknown, ctx: JsonContext): void => {
139
+ const expected: PropertyDefinition[] = [
140
+ { property: "containment", expectedType: "object", mayBeNull: NOT_NULL, validateValue: this.validateLwMetaPointer },
141
+ { property: "children", expectedType: "array", mayBeNull: NOT_NULL, validateValue: this.checkChild }
142
+ ];
143
+ this.propertyChecks(obj, expected, ctx);
144
+ }
145
+
146
+ private checkChild = (obj: unknown, context: JsonContext) => {
147
+ if (this.checkType(obj, "string", context)) {
148
+ this.simpleFieldValidator.validateId(obj as string, context);
149
+ }
150
+ }
151
+
152
+ /** Checks whether `obj` is not null or defined and has the correct type.
153
+ */
154
+ private checkType = (obj: unknown, expectedType: PropertyType, context: JsonContext): boolean => {
155
+ if (obj === null || obj === undefined) {
156
+ this.validationResult.issue(new Syntax_PropertyTypeIssue(context, "obj", expectedType, typeof obj));
157
+ return false;
158
+ } else if (typeof obj !== expectedType) {
159
+ // TODO Better context: where does obj come from
160
+ this.validationResult.issue(new Syntax_PropertyTypeIssue(context, "obj", expectedType, typeof obj));
161
+ return false;
162
+ }
163
+ return true;
164
+ }
165
+
166
+ validateLwReference = (obj: unknown, ctx: JsonContext): void => {
167
+ const expected: PropertyDefinition[] = [
168
+ { property: "reference", expectedType: "object", mayBeNull: NOT_NULL, validateValue: this.validateLwMetaPointer },
169
+ { property: "targets", expectedType: "array", mayBeNull: NOT_NULL, validateValue: this.validateLwReferenceTarget }
170
+ ];
171
+ this.propertyChecks(obj, expected, ctx);
172
+ }
173
+
174
+ validateLwReferenceTarget = (obj: unknown, ctx: JsonContext): void => {
175
+ const expected: PropertyDefinition[] = [
176
+ { property: "resolveInfo", expectedType: "string", mayBeNull: MAY_BE_NULL },
177
+ { property: "reference", expectedType: "string", mayBeNull: MAY_BE_NULL, validateValue: this.simpleFieldValidator.validateId }
178
+ ];
179
+ this.propertyChecks(obj, expected, ctx);
180
+ }
181
+
182
+ /**
183
+ * Check whether all property definitions in `propDef` are correct and check that there are
184
+ * no iother properties in `obj`.
185
+ * @param obj
186
+ * @param propDefs
187
+ * @param context
188
+ */
189
+ propertyChecks(obj: unknown, propDefs: PropertyDefinition[], context: JsonContext): void {
190
+ if (!this.checkType(obj, "object", context)) {
191
+ // console.log("UNEXPECTED NULL OBJECT");
192
+ return;
193
+ }
194
+ const object = obj as UnknownObjectType;
195
+ const allProperties: string[] = [];
196
+ propDefs.forEach( (propDef) => {
197
+ if (propDef.property === "key") {
198
+ // console.log("CHECKING KEY of " + JSON.stringify(obj))
199
+ }
200
+ if (this.checkPropertyType(object, propDef.property, propDef.expectedType, propDef.mayBeNull, context.concat(propDef.property))) {
201
+ const propValue = object[propDef.property];
202
+ if (this.recursive && propDef.expectedType === "array" && Array.isArray(propValue) && !!propDef.validateValue) {
203
+ propValue.forEach((arrayItem: unknown, index: number) => {
204
+ if (arrayItem === null) {
205
+ this.validationResult.issue(new Syntax_ArrayContainsNull_Issue(context.concat(propDef.property, index), propDef.property, index));
206
+ } else {
207
+ if (propDef.validateValue !== null && propDef.validateValue !== undefined ) {
208
+ propDef.validateValue(arrayItem, context.concat(propDef.property, index));
209
+ } else {
210
+ // TODO: give an error, whih ine?
211
+ }
212
+ }
213
+ });
214
+ } else if (propDef.validateValue !== null && propDef.validateValue !== undefined) {
215
+ // propValue is niot an array, so it should be aa string
216
+ propDef.validateValue(propValue as string, context.concat(propDef.property));
217
+ }
218
+ }
219
+ allProperties.push(propDef.property);
220
+ });
221
+ this.checkStrayProperties(object, allProperties, context);
222
+ }
223
+
224
+ /**
225
+ * Check whether there are extra properties that should not be there.
226
+ * @param obj
227
+ * @param properties
228
+ * @param context
229
+ */
230
+ checkStrayProperties(obj: UnknownObjectType, properties: string[], context: JsonContext) {
231
+
232
+ const own = Object.getOwnPropertyNames(obj);
233
+ own.forEach((ownProp) => {
234
+ if (!properties.includes(ownProp)) {
235
+ this.validationResult.issue(new Syntax_PropertyUnknownIssue(context, ownProp));
236
+ }
237
+ });
238
+ properties.forEach((prop) => {
239
+ if (!own.includes(prop)) {
240
+ this.validationResult.issue(new Syntax_PropertyMissingIssue(context, prop));
241
+ }
242
+ });
243
+ }
244
+
245
+ /**
246
+ * Check whether the value of property `prop` of `obj` has type `expectedType`.
247
+ * @param obj
248
+ * @param prop
249
+ * @param expectedType
250
+ * @param context
251
+ */
252
+ checkPropertyType = (obj: UnknownObjectType, prop: string, expectedType: PropertyType, mayBeNull: boolean, context: JsonContext): boolean => {
253
+ if (prop === "key") {
254
+ // console.log(" checking type of key " + JSON.stringify(obj));
255
+ }
256
+ if (obj[prop] === undefined || obj[prop] === null) {
257
+ if (!mayBeNull) {
258
+ this.validationResult.issue(new Syntax_PropertyNullIssue(context, prop));
259
+ return false;
260
+ } else {
261
+ return true;
262
+ }
263
+ } else {
264
+ const actualType = typeof obj[prop];
265
+ if (expectedType !== actualType) {
266
+ if (expectedType === "array" && actualType === "object") {
267
+ // typeof returns an object for an array, so we need to check this separately.
268
+ if (!Array.isArray(obj[prop])) {
269
+ this.validationResult.issue(new Syntax_PropertyTypeIssue(context, prop, "array", typeof obj[prop]));
270
+ return false;
271
+ } else {
272
+ return true;
273
+ }
274
+ } else {
275
+ this.validationResult.issue(new Syntax_PropertyTypeIssue(context, prop, expectedType, actualType));
276
+ return false;
277
+ }
278
+ } else {
279
+ if (expectedType === "object") {
280
+ // typeof returns an object for an array, so we need to check this separately.
281
+ if (Array.isArray(obj[prop])) {
282
+ this.validationResult.issue(new Syntax_PropertyTypeIssue(context, prop, expectedType, "array"));
283
+ return false;
284
+ }
285
+ }
286
+ }
287
+ }
288
+ return true;
289
+ }
290
+ }
291
+
292
+ export function SyntaxValidator(jsonChunk:unknown): ValidationResult {
293
+ const validationResult = new ValidationResult();
294
+ const syntaxValidator = new LionWebSyntaxValidator(validationResult);
295
+ syntaxValidator.validate(jsonChunk);
296
+ return validationResult;
297
+ }
@@ -0,0 +1,76 @@
1
+ import { LionWebJsonChunk } from "../json/LionWebJson";
2
+ import { LionWebJsonChunkWrapper } from "../json/LionWebJsonChunkWrapper";
3
+ import { LionWebLanguageDefinition } from "../json/LionWebLanguageDefinition";
4
+ import { LionWebLanguageReferenceValidator } from "./LionWebLanguageReferenceValidator";
5
+ import { LionWebReferenceValidator } from "./LionWebReferenceValidator";
6
+ import { LionWebSyntaxValidator } from "./LionWebSyntaxValidator";
7
+ import { ValidationResult } from "./ValidationResult";
8
+
9
+ /**
10
+ * Combined validator that calls all available validators.
11
+ * Will stop when one validator fails.
12
+ */
13
+ export class LionWebValidator {
14
+ object: unknown;
15
+ language: LionWebLanguageDefinition | null = null;
16
+
17
+ chunk: unknown;
18
+ validationResult: ValidationResult;
19
+ syntaxValidator: LionWebSyntaxValidator;
20
+ referenceValidator: LionWebReferenceValidator;
21
+ syntaxCorrect: boolean = false;
22
+ referencesCorrect: boolean = false;
23
+
24
+ constructor(json: unknown, lang: LionWebLanguageDefinition | null) {
25
+ this.object = json;
26
+ this.language =lang;
27
+ this.validationResult = new ValidationResult();
28
+ this.syntaxValidator = new LionWebSyntaxValidator(this.validationResult);
29
+ this.referenceValidator = new LionWebReferenceValidator(this.validationResult);
30
+ }
31
+
32
+ validateAll() {
33
+ this.validateSyntax();
34
+ this.validateReferences();
35
+ this.validateForLanguage();
36
+ }
37
+
38
+ validateSyntax() {
39
+ this.syntaxValidator.recursive = true;
40
+ this.syntaxValidator.validate(this.object);
41
+ this.syntaxCorrect = !this.validationResult.hasErrors();
42
+ if (this.syntaxCorrect) {
43
+ this.chunk = new LionWebJsonChunkWrapper(this.object as LionWebJsonChunk);
44
+ }
45
+ }
46
+
47
+ validateReferences(): void {
48
+ if (!this.syntaxCorrect) {
49
+ // console.log("validateReferences not executed because there are syntax errors.")
50
+ return;
51
+ }
52
+ // when syntax is correct we know the chunk is actually a chunk!
53
+ this.referenceValidator.validate(this.chunk as LionWebJsonChunkWrapper);
54
+ this.referencesCorrect = !this.validationResult.hasErrors()
55
+ }
56
+
57
+ validateForLanguage(): void {
58
+ if (!this.syntaxCorrect) {
59
+ // console.log("validateForLanguage not executed because there are syntax errors.")
60
+ return;
61
+ }
62
+ if (!this.referencesCorrect) {
63
+ // console.log("validateForLanguage not executed because there are reference errors.")
64
+ return;
65
+ }
66
+ if (this.language !== null) {
67
+ const languageReferenceValidator = new LionWebLanguageReferenceValidator(this.validationResult, this.language);
68
+ // when syntax is correct we know the chunk is actually a chunk!
69
+ languageReferenceValidator.validate(this.chunk as LionWebJsonChunkWrapper);
70
+ }
71
+ }
72
+
73
+ // setLanguage(json: LionwebLanguageDefinition) {
74
+ // this.language = json;
75
+ // }
76
+ }