@canon-protocol/sdk 8.0.0
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.
- package/dist/canons/Canon.d.ts +2 -0
- package/dist/canons/Canon.js +2 -0
- package/dist/canons/DefinedCanon.d.ts +5 -0
- package/dist/canons/DefinedCanon.js +4 -0
- package/dist/canons/EmbeddedCanon.d.ts +3 -0
- package/dist/canons/EmbeddedCanon.js +3 -0
- package/dist/canons/ReferenceCanon.d.ts +6 -0
- package/dist/canons/ReferenceCanon.js +10 -0
- package/dist/canons/SubjectCanon.d.ts +6 -0
- package/dist/canons/SubjectCanon.js +6 -0
- package/dist/canons/index.d.ts +5 -0
- package/dist/canons/index.js +5 -0
- package/dist/ctl/CtlCanonUri.d.ts +28 -0
- package/dist/ctl/CtlCanonUri.js +230 -0
- package/dist/ctl/CtlGraphResolver.d.ts +14 -0
- package/dist/ctl/CtlGraphResolver.js +411 -0
- package/dist/ctl/CtlMarkdownRenderer.d.ts +17 -0
- package/dist/ctl/CtlMarkdownRenderer.js +262 -0
- package/dist/ctl/CtlParser.d.ts +10 -0
- package/dist/ctl/CtlParser.js +331 -0
- package/dist/ctl/CtlValidator.d.ts +12 -0
- package/dist/ctl/CtlValidator.js +207 -0
- package/dist/ctl/ResourceTypeClassifier.d.ts +21 -0
- package/dist/ctl/ResourceTypeClassifier.js +126 -0
- package/dist/ctl/index.d.ts +12 -0
- package/dist/ctl/index.js +9 -0
- package/dist/filtering/GitIgnoreFilter.d.ts +7 -0
- package/dist/filtering/GitIgnoreFilter.js +32 -0
- package/dist/filtering/IGitIgnoreFilter.d.ts +3 -0
- package/dist/filtering/IGitIgnoreFilter.js +1 -0
- package/dist/filtering/index.d.ts +2 -0
- package/dist/filtering/index.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +8 -0
- package/dist/parsing/CanonObjectParser.d.ts +21 -0
- package/dist/parsing/CanonObjectParser.js +242 -0
- package/dist/parsing/CanonParser.d.ts +21 -0
- package/dist/parsing/CanonParser.js +457 -0
- package/dist/parsing/ICanonObjectParser.d.ts +7 -0
- package/dist/parsing/ICanonObjectParser.js +1 -0
- package/dist/parsing/PropertyMetadata.d.ts +7 -0
- package/dist/parsing/PropertyMetadata.js +7 -0
- package/dist/parsing/index.d.ts +6 -0
- package/dist/parsing/index.js +3 -0
- package/dist/repositories/CompositeCanonDocumentRepository.d.ts +28 -0
- package/dist/repositories/CompositeCanonDocumentRepository.js +125 -0
- package/dist/repositories/FileSystemCanonDocumentRepository.d.ts +28 -0
- package/dist/repositories/FileSystemCanonDocumentRepository.js +295 -0
- package/dist/repositories/HttpCanonDocumentRepository.d.ts +24 -0
- package/dist/repositories/HttpCanonDocumentRepository.js +79 -0
- package/dist/repositories/InMemoryCanonDocumentRepository.d.ts +23 -0
- package/dist/repositories/InMemoryCanonDocumentRepository.js +149 -0
- package/dist/repositories/PublisherConfig.d.ts +12 -0
- package/dist/repositories/PublisherConfig.js +62 -0
- package/dist/repositories/PublisherIndex.d.ts +20 -0
- package/dist/repositories/PublisherIndex.js +127 -0
- package/dist/repositories/RepositoryFactory.d.ts +6 -0
- package/dist/repositories/RepositoryFactory.js +13 -0
- package/dist/repositories/index.d.ts +9 -0
- package/dist/repositories/index.js +7 -0
- package/dist/resolution/CanonUri.d.ts +10 -0
- package/dist/resolution/CanonUri.js +55 -0
- package/dist/resolution/CanonUriBuilder.d.ts +12 -0
- package/dist/resolution/CanonUriBuilder.js +51 -0
- package/dist/resolution/ResourceResolutionResult.d.ts +9 -0
- package/dist/resolution/ResourceResolutionResult.js +1 -0
- package/dist/resolution/ResourceResolver.d.ts +27 -0
- package/dist/resolution/ResourceResolver.js +266 -0
- package/dist/resolution/TypeResolver.d.ts +15 -0
- package/dist/resolution/TypeResolver.js +110 -0
- package/dist/resolution/index.d.ts +6 -0
- package/dist/resolution/index.js +4 -0
- package/dist/statements/BooleanStatement.d.ts +3 -0
- package/dist/statements/BooleanStatement.js +3 -0
- package/dist/statements/EmbeddedStatement.d.ts +4 -0
- package/dist/statements/EmbeddedStatement.js +3 -0
- package/dist/statements/IStatement.d.ts +2 -0
- package/dist/statements/IStatement.js +1 -0
- package/dist/statements/ListStatement.d.ts +4 -0
- package/dist/statements/ListStatement.js +3 -0
- package/dist/statements/NumberStatement.d.ts +4 -0
- package/dist/statements/NumberStatement.js +10 -0
- package/dist/statements/ReferenceStatement.d.ts +5 -0
- package/dist/statements/ReferenceStatement.js +10 -0
- package/dist/statements/ScalarStatement.d.ts +3 -0
- package/dist/statements/ScalarStatement.js +3 -0
- package/dist/statements/Statement.d.ts +6 -0
- package/dist/statements/Statement.js +4 -0
- package/dist/statements/StringStatement.d.ts +4 -0
- package/dist/statements/StringStatement.js +10 -0
- package/dist/statements/index.d.ts +9 -0
- package/dist/statements/index.js +8 -0
- package/dist/validation/CanonObjectValidator.d.ts +21 -0
- package/dist/validation/CanonObjectValidator.js +150 -0
- package/dist/validation/ICanonObjectValidator.d.ts +7 -0
- package/dist/validation/ICanonObjectValidator.js +1 -0
- package/dist/validation/OntologyValidationError.d.ts +15 -0
- package/dist/validation/OntologyValidationError.js +25 -0
- package/dist/validation/OntologyValidationResult.d.ts +7 -0
- package/dist/validation/OntologyValidationResult.js +8 -0
- package/dist/validation/ValidationContext.d.ts +6 -0
- package/dist/validation/ValidationContext.js +10 -0
- package/dist/validation/ValidationSeverity.d.ts +4 -0
- package/dist/validation/ValidationSeverity.js +5 -0
- package/dist/validation/index.d.ts +10 -0
- package/dist/validation/index.js +7 -0
- package/dist/validation/rules/document/EmbeddedCanonNoExplicitTypeRule.d.ts +8 -0
- package/dist/validation/rules/document/EmbeddedCanonNoExplicitTypeRule.js +53 -0
- package/dist/validation/rules/document/IDocumentValidationRule.d.ts +6 -0
- package/dist/validation/rules/document/IDocumentValidationRule.js +1 -0
- package/dist/validation/rules/document/NamespacePrefixRule.d.ts +10 -0
- package/dist/validation/rules/document/NamespacePrefixRule.js +51 -0
- package/dist/validation/rules/document/PropertyTypeSpecificityRule.d.ts +11 -0
- package/dist/validation/rules/document/PropertyTypeSpecificityRule.js +89 -0
- package/dist/validation/rules/document/ResourceNamingRule.d.ts +12 -0
- package/dist/validation/rules/document/ResourceNamingRule.js +79 -0
- package/dist/validation/rules/document/SubjectCanonTypeRequiredRule.d.ts +7 -0
- package/dist/validation/rules/document/SubjectCanonTypeRequiredRule.js +33 -0
- package/dist/validation/rules/document/index.d.ts +6 -0
- package/dist/validation/rules/document/index.js +5 -0
- package/dist/validation/rules/normalizeToStringList.d.ts +1 -0
- package/dist/validation/rules/normalizeToStringList.js +9 -0
- package/dist/validation/rules/repository/AmbiguousReferenceRule.d.ts +14 -0
- package/dist/validation/rules/repository/AmbiguousReferenceRule.js +155 -0
- package/dist/validation/rules/repository/ClassDefinitionRule.d.ts +12 -0
- package/dist/validation/rules/repository/ClassDefinitionRule.js +205 -0
- package/dist/validation/rules/repository/ClassHierarchyCycleRule.d.ts +11 -0
- package/dist/validation/rules/repository/ClassHierarchyCycleRule.js +98 -0
- package/dist/validation/rules/repository/DefinitionPropertyReferenceRule.d.ts +13 -0
- package/dist/validation/rules/repository/DefinitionPropertyReferenceRule.js +162 -0
- package/dist/validation/rules/repository/IRepositoryValidationRule.d.ts +7 -0
- package/dist/validation/rules/repository/IRepositoryValidationRule.js +1 -0
- package/dist/validation/rules/repository/ImportExistenceRule.d.ts +12 -0
- package/dist/validation/rules/repository/ImportExistenceRule.js +124 -0
- package/dist/validation/rules/repository/InstancePropertyReferenceRule.d.ts +13 -0
- package/dist/validation/rules/repository/InstancePropertyReferenceRule.js +161 -0
- package/dist/validation/rules/repository/NamespaceImportCycleRule.d.ts +11 -0
- package/dist/validation/rules/repository/NamespaceImportCycleRule.js +85 -0
- package/dist/validation/rules/repository/ObjectPropertyImportRule.d.ts +9 -0
- package/dist/validation/rules/repository/ObjectPropertyImportRule.js +113 -0
- package/dist/validation/rules/repository/ObjectPropertyValueValidationRule.d.ts +12 -0
- package/dist/validation/rules/repository/ObjectPropertyValueValidationRule.js +281 -0
- package/dist/validation/rules/repository/PropertyDomainRule.d.ts +14 -0
- package/dist/validation/rules/repository/PropertyDomainRule.js +221 -0
- package/dist/validation/rules/repository/PropertyHierarchyCycleRule.d.ts +11 -0
- package/dist/validation/rules/repository/PropertyHierarchyCycleRule.js +104 -0
- package/dist/validation/rules/repository/PropertyRangeReferenceRule.d.ts +13 -0
- package/dist/validation/rules/repository/PropertyRangeReferenceRule.js +184 -0
- package/dist/validation/rules/repository/PropertyRangeRequiredRule.d.ts +8 -0
- package/dist/validation/rules/repository/PropertyRangeRequiredRule.js +40 -0
- package/dist/validation/rules/repository/PropertyValueTypeRule.d.ts +11 -0
- package/dist/validation/rules/repository/PropertyValueTypeRule.js +171 -0
- package/dist/validation/rules/repository/SubClassOfReferenceRule.d.ts +11 -0
- package/dist/validation/rules/repository/SubClassOfReferenceRule.js +133 -0
- package/dist/validation/rules/repository/SubPropertyOfReferenceRule.d.ts +11 -0
- package/dist/validation/rules/repository/SubPropertyOfReferenceRule.js +133 -0
- package/dist/validation/rules/repository/TypeAmbiguityRule.d.ts +10 -0
- package/dist/validation/rules/repository/TypeAmbiguityRule.js +104 -0
- package/dist/validation/rules/repository/UnresolvedReferenceRule.d.ts +11 -0
- package/dist/validation/rules/repository/UnresolvedReferenceRule.js +91 -0
- package/dist/validation/rules/repository/XsdImportRule.d.ts +11 -0
- package/dist/validation/rules/repository/XsdImportRule.js +125 -0
- package/dist/validation/rules/repository/index.d.ts +20 -0
- package/dist/validation/rules/repository/index.js +19 -0
- package/package.json +82 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
2
|
+
import { ValidationSeverity } from '../../ValidationSeverity.js';
|
|
3
|
+
export class PropertyRangeReferenceRule {
|
|
4
|
+
builtInClasses = new Set([
|
|
5
|
+
'Resource',
|
|
6
|
+
'Class',
|
|
7
|
+
'Literal',
|
|
8
|
+
'Thing',
|
|
9
|
+
'Nothing',
|
|
10
|
+
'Property',
|
|
11
|
+
'DatatypeProperty',
|
|
12
|
+
'ObjectProperty',
|
|
13
|
+
'AnnotationProperty'
|
|
14
|
+
]);
|
|
15
|
+
primitiveTypes = new Set([
|
|
16
|
+
'string', 'integer', 'int', 'long', 'short', 'byte',
|
|
17
|
+
'decimal', 'float', 'double', 'boolean', 'bool',
|
|
18
|
+
'dateTime', 'date', 'time', 'duration',
|
|
19
|
+
'anyURI', 'anySimpleType',
|
|
20
|
+
'nonNegativeInteger', 'positiveInteger',
|
|
21
|
+
'negativeInteger', 'nonPositiveInteger',
|
|
22
|
+
'unsignedInt', 'unsignedLong', 'unsignedShort', 'unsignedByte',
|
|
23
|
+
'base64Binary', 'hexBinary',
|
|
24
|
+
'Literal', 'Resource'
|
|
25
|
+
]);
|
|
26
|
+
get ruleName() {
|
|
27
|
+
return 'PropertyRangeReference';
|
|
28
|
+
}
|
|
29
|
+
async validateAsync(document, repository) {
|
|
30
|
+
const errors = [];
|
|
31
|
+
const definedTypes = new Set();
|
|
32
|
+
for (const [entityName, entityDef] of Object.entries(document.body)) {
|
|
33
|
+
if (typeof entityDef === 'object' && entityDef !== null && !Array.isArray(entityDef)) {
|
|
34
|
+
const entity = entityDef;
|
|
35
|
+
const type = entity['type']?.toString();
|
|
36
|
+
if (this.isValidRangeTargetType(type)) {
|
|
37
|
+
definedTypes.add(entityName);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const [entityName, entityDef] of Object.entries(document.body)) {
|
|
42
|
+
if (typeof entityDef === 'object' && entityDef !== null && !Array.isArray(entityDef)) {
|
|
43
|
+
const entity = entityDef;
|
|
44
|
+
const type = entity['type']?.toString();
|
|
45
|
+
if (type !== 'DatatypeProperty' &&
|
|
46
|
+
type !== 'ObjectProperty' &&
|
|
47
|
+
!type?.endsWith('.DatatypeProperty') &&
|
|
48
|
+
!type?.endsWith('.ObjectProperty')) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const rangeValue = entity['range'];
|
|
52
|
+
if (!rangeValue) {
|
|
53
|
+
continue; // PropertyRangeRequiredRule handles missing range
|
|
54
|
+
}
|
|
55
|
+
const rangeTypes = [];
|
|
56
|
+
if (Array.isArray(rangeValue)) {
|
|
57
|
+
for (const item of rangeValue) {
|
|
58
|
+
const itemStr = item?.toString();
|
|
59
|
+
if (itemStr) {
|
|
60
|
+
rangeTypes.push(itemStr);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const rangeStr = rangeValue?.toString();
|
|
66
|
+
if (rangeStr) {
|
|
67
|
+
rangeTypes.push(rangeStr);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (rangeTypes.length === 0) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const rangeType of rangeTypes) {
|
|
74
|
+
if (this.primitiveTypes.has(rangeType)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (this.builtInClasses.has(rangeType)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (definedTypes.has(rangeType)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const isAvailable = await this.isTypeAvailableInImports(document, repository, rangeType);
|
|
84
|
+
if (!isAvailable) {
|
|
85
|
+
const propertyType = type?.includes('ObjectProperty') ? 'ObjectProperty' : 'DatatypeProperty';
|
|
86
|
+
const error = new OntologyValidationError();
|
|
87
|
+
error.ruleType = this.ruleName;
|
|
88
|
+
error.severity = ValidationSeverity.Error;
|
|
89
|
+
error.message = `Property '${entityName}' has range '${rangeType}' which is not defined or imported`;
|
|
90
|
+
error.suggestion =
|
|
91
|
+
propertyType === 'ObjectProperty'
|
|
92
|
+
? `For ObjectProperty:\n` +
|
|
93
|
+
` 1. Define '${rangeType}' as a Class in this namespace, OR\n` +
|
|
94
|
+
` 2. Import the namespace that contains '${rangeType}', OR\n` +
|
|
95
|
+
` 3. Use a qualified reference like 'alias.${rangeType}' if already imported with an alias`
|
|
96
|
+
: `For DatatypeProperty:\n` +
|
|
97
|
+
` 1. Use a primitive type (string, integer, boolean, dateTime, etc.), OR\n` +
|
|
98
|
+
` 2. If '${rangeType}' should be a class, change property type to ObjectProperty`;
|
|
99
|
+
error.entityName = entityName;
|
|
100
|
+
error.propertyName = 'range';
|
|
101
|
+
error.actualValue = rangeType;
|
|
102
|
+
error.expectedValue =
|
|
103
|
+
propertyType === 'ObjectProperty' ? 'A defined or imported class name' : 'A primitive type or defined class';
|
|
104
|
+
errors.push(error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return errors;
|
|
110
|
+
}
|
|
111
|
+
async isTypeAvailableInImports(document, repository, typeName) {
|
|
112
|
+
if (!document.metadata.imports) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
let namespaceAlias = null;
|
|
116
|
+
let actualTypeName = typeName;
|
|
117
|
+
if (typeName.includes('.')) {
|
|
118
|
+
const parts = typeName.split('.', 2);
|
|
119
|
+
namespaceAlias = parts[0];
|
|
120
|
+
actualTypeName = parts[1];
|
|
121
|
+
}
|
|
122
|
+
for (const [publisher, imports] of Object.entries(document.metadata.imports)) {
|
|
123
|
+
for (const import_ of imports) {
|
|
124
|
+
if (namespaceAlias !== null && import_.alias !== namespaceAlias) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const importedDoc = await repository.getHighestCompatibleVersionAsync(publisher, import_);
|
|
128
|
+
if (importedDoc) {
|
|
129
|
+
const lookupName = namespaceAlias !== null ? actualTypeName : typeName;
|
|
130
|
+
const entityDef = importedDoc.body[lookupName];
|
|
131
|
+
if (entityDef && typeof entityDef === 'object' && !Array.isArray(entityDef)) {
|
|
132
|
+
const entity = entityDef;
|
|
133
|
+
const type = entity['type']?.toString();
|
|
134
|
+
if (this.isValidRangeTargetType(type)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const visitedDocs = new Set();
|
|
139
|
+
if (await this.isTypeAvailableInImportsRecursive(importedDoc, repository, lookupName, visitedDocs)) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
async isTypeAvailableInImportsRecursive(document, repository, typeName, visitedDocs) {
|
|
148
|
+
const docId = document.metadata.namespace_?.toString() ?? '';
|
|
149
|
+
if (docId && visitedDocs.has(docId)) {
|
|
150
|
+
return false; // Already checked this document
|
|
151
|
+
}
|
|
152
|
+
if (docId) {
|
|
153
|
+
visitedDocs.add(docId);
|
|
154
|
+
}
|
|
155
|
+
if (!document.metadata.imports) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
for (const [publisher, imports] of Object.entries(document.metadata.imports)) {
|
|
159
|
+
for (const import_ of imports) {
|
|
160
|
+
const importedDoc = await repository.getHighestCompatibleVersionAsync(publisher, import_);
|
|
161
|
+
if (importedDoc) {
|
|
162
|
+
const entityDef = importedDoc.body[typeName];
|
|
163
|
+
if (entityDef && typeof entityDef === 'object' && !Array.isArray(entityDef)) {
|
|
164
|
+
const entity = entityDef;
|
|
165
|
+
const type = entity['type']?.toString();
|
|
166
|
+
if (this.isValidRangeTargetType(type)) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (await this.isTypeAvailableInImportsRecursive(importedDoc, repository, typeName, visitedDocs)) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
isValidRangeTargetType(type) {
|
|
179
|
+
if (!type)
|
|
180
|
+
return false;
|
|
181
|
+
return type === 'Class' || type.endsWith('.Class')
|
|
182
|
+
|| type === 'Datatype' || type.endsWith('.Datatype');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import type { ICanonDocumentRepository } from '@canon-protocol/types/document/models';
|
|
3
|
+
import type { IRepositoryValidationRule } from './IRepositoryValidationRule.js';
|
|
4
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
5
|
+
export declare class PropertyRangeRequiredRule implements IRepositoryValidationRule {
|
|
6
|
+
get ruleName(): string;
|
|
7
|
+
validateAsync(document: CanonDocument, _repository: ICanonDocumentRepository): Promise<OntologyValidationError[]>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
2
|
+
import { ValidationSeverity } from '../../ValidationSeverity.js';
|
|
3
|
+
export class PropertyRangeRequiredRule {
|
|
4
|
+
get ruleName() {
|
|
5
|
+
return 'PropertyRangeRequired';
|
|
6
|
+
}
|
|
7
|
+
async validateAsync(document, _repository) {
|
|
8
|
+
const errors = [];
|
|
9
|
+
for (const [entityName, entityDef] of Object.entries(document.body)) {
|
|
10
|
+
if (typeof entityDef !== 'object' || entityDef === null || Array.isArray(entityDef)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const entity = entityDef;
|
|
14
|
+
const type = entity['type']?.toString();
|
|
15
|
+
if (type === 'DatatypeProperty' || type === 'ObjectProperty' ||
|
|
16
|
+
type?.endsWith('.DatatypeProperty') || type?.endsWith('.ObjectProperty')) {
|
|
17
|
+
const hasRange = 'range' in entity;
|
|
18
|
+
const rangeValue = entity['range']?.toString().trim();
|
|
19
|
+
if (!hasRange || !rangeValue) {
|
|
20
|
+
const error = new OntologyValidationError();
|
|
21
|
+
error.ruleType = this.ruleName;
|
|
22
|
+
error.severity = ValidationSeverity.Error;
|
|
23
|
+
error.entityName = entityName;
|
|
24
|
+
error.propertyName = 'range';
|
|
25
|
+
error.message = `Property '${entityName}' must have a 'range' defined.`;
|
|
26
|
+
error.suggestion =
|
|
27
|
+
type === 'DatatypeProperty'
|
|
28
|
+
? 'Add a range like: range: string (or integer, boolean, etc.)'
|
|
29
|
+
: 'Add a range like: range: ClassName (the class this property expects)';
|
|
30
|
+
error.expectedValue =
|
|
31
|
+
type === 'DatatypeProperty'
|
|
32
|
+
? 'A datatype (string, integer, boolean, decimal, etc.)'
|
|
33
|
+
: 'A class name (Character, Scene, Story, etc.)';
|
|
34
|
+
errors.push(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return errors;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import type { ICanonDocumentRepository } from '@canon-protocol/types/document/models';
|
|
3
|
+
import type { IRepositoryValidationRule } from './IRepositoryValidationRule.js';
|
|
4
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
5
|
+
export declare class PropertyValueTypeRule implements IRepositoryValidationRule {
|
|
6
|
+
get ruleName(): string;
|
|
7
|
+
validateAsync(document: CanonDocument, _repository: ICanonDocumentRepository): Promise<OntologyValidationError[]>;
|
|
8
|
+
private validateDatatypeValue;
|
|
9
|
+
private getValueTypeName;
|
|
10
|
+
private getPropertyValue;
|
|
11
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { ValidationSeverity } from '../../ValidationSeverity.js';
|
|
2
|
+
export class PropertyValueTypeRule {
|
|
3
|
+
get ruleName() {
|
|
4
|
+
return 'PropertyValueType';
|
|
5
|
+
}
|
|
6
|
+
async validateAsync(document, _repository) {
|
|
7
|
+
const errors = [];
|
|
8
|
+
const propertyDefinitions = new Map();
|
|
9
|
+
for (const [entityName, entityDef] of Object.entries(document.body)) {
|
|
10
|
+
if (typeof entityDef === 'object' && entityDef !== null && !Array.isArray(entityDef)) {
|
|
11
|
+
const entity = entityDef;
|
|
12
|
+
const type = this.getPropertyValue(entity, 'type');
|
|
13
|
+
if (type === 'DatatypeProperty' || type === 'ObjectProperty') {
|
|
14
|
+
propertyDefinitions.set(entityName, entity);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (const [entityName, entityDef] of Object.entries(document.body)) {
|
|
19
|
+
if (typeof entityDef !== 'object' || entityDef === null || Array.isArray(entityDef)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const entity = entityDef;
|
|
23
|
+
const entityType = this.getPropertyValue(entity, 'type');
|
|
24
|
+
if (!entityType ||
|
|
25
|
+
entityType === 'Class' ||
|
|
26
|
+
entityType.endsWith('Property') ||
|
|
27
|
+
entityType === 'AnnotationProperty') {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
for (const [propName, propValue] of Object.entries(entity)) {
|
|
31
|
+
if (propName === 'type' || propName === 'label' || propName === 'comment') {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
let lookupName = propName;
|
|
35
|
+
const dotIndex = propName.lastIndexOf('.');
|
|
36
|
+
if (dotIndex > 0 && dotIndex < propName.length - 1) {
|
|
37
|
+
lookupName = propName.substring(dotIndex + 1);
|
|
38
|
+
}
|
|
39
|
+
const propDef = propertyDefinitions.get(lookupName);
|
|
40
|
+
if (!propDef) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const propertyType = this.getPropertyValue(propDef, 'type');
|
|
44
|
+
const propertyRange = this.getPropertyValue(propDef, 'range');
|
|
45
|
+
if (propertyType === 'DatatypeProperty') {
|
|
46
|
+
if ((typeof propValue === 'object' && propValue !== null && !Array.isArray(propValue)) ||
|
|
47
|
+
Array.isArray(propValue)) {
|
|
48
|
+
errors.push({
|
|
49
|
+
ruleType: this.ruleName,
|
|
50
|
+
severity: ValidationSeverity.Error,
|
|
51
|
+
entityName: entityName,
|
|
52
|
+
propertyName: propName,
|
|
53
|
+
message: `Property '${propName}' is a DatatypeProperty with range '${propertyRange}' but instance '${entityName}' has a complex object value`,
|
|
54
|
+
suggestion: `Either:\n` +
|
|
55
|
+
` 1. Create a class for this data and change '${propName}' to an ObjectProperty\n` +
|
|
56
|
+
` 2. Store as a JSON string to keep it as DatatypeProperty\n` +
|
|
57
|
+
` 3. Use separate properties instead of nesting`,
|
|
58
|
+
expectedValue: `A simple value of type '${propertyRange}'`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else if (!this.validateDatatypeValue(propertyRange, propValue)) {
|
|
62
|
+
const error = {
|
|
63
|
+
ruleType: this.ruleName,
|
|
64
|
+
severity: ValidationSeverity.Error,
|
|
65
|
+
entityName: entityName,
|
|
66
|
+
propertyName: propName,
|
|
67
|
+
message: `Property '${propName}' expects type '${propertyRange}' but instance '${entityName}' has value of type '${this.getValueTypeName(propValue)}'`,
|
|
68
|
+
suggestion: `Provide a value of type '${propertyRange}'`,
|
|
69
|
+
toString: () => ''
|
|
70
|
+
};
|
|
71
|
+
if (propertyRange) {
|
|
72
|
+
error.expectedValue = propertyRange;
|
|
73
|
+
}
|
|
74
|
+
errors.push(error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (propertyType === 'ObjectProperty') {
|
|
78
|
+
if (Array.isArray(propValue)) {
|
|
79
|
+
for (let i = 0; i < propValue.length; i++) {
|
|
80
|
+
const item = propValue[i];
|
|
81
|
+
if (typeof item !== 'string' &&
|
|
82
|
+
(typeof item !== 'object' || item === null || Array.isArray(item))) {
|
|
83
|
+
errors.push({
|
|
84
|
+
ruleType: this.ruleName,
|
|
85
|
+
severity: ValidationSeverity.Error,
|
|
86
|
+
entityName: entityName,
|
|
87
|
+
propertyName: `${propName}[${i}]`,
|
|
88
|
+
message: `ObjectProperty '${propName}' array item ${i} in instance '${entityName}' has invalid type '${this.getValueTypeName(item)}'`,
|
|
89
|
+
suggestion: 'Array items must be references (strings) or embedded objects (dictionaries)',
|
|
90
|
+
expectedValue: 'string or object'
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (typeof propValue !== 'string' &&
|
|
96
|
+
(typeof propValue !== 'object' || propValue === null || Array.isArray(propValue))) {
|
|
97
|
+
errors.push({
|
|
98
|
+
ruleType: this.ruleName,
|
|
99
|
+
severity: ValidationSeverity.Error,
|
|
100
|
+
entityName: entityName,
|
|
101
|
+
propertyName: propName,
|
|
102
|
+
message: `ObjectProperty '${propName}' in instance '${entityName}' has invalid value type '${this.getValueTypeName(propValue)}'`,
|
|
103
|
+
suggestion: 'Value must be a reference (string), embedded object (dictionary), or array of these',
|
|
104
|
+
expectedValue: `Reference to ${propertyRange} or embedded ${propertyRange} object`
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return errors;
|
|
111
|
+
}
|
|
112
|
+
validateDatatypeValue(range, value) {
|
|
113
|
+
const rangeLower = range?.toLowerCase();
|
|
114
|
+
switch (rangeLower) {
|
|
115
|
+
case 'string':
|
|
116
|
+
return typeof value === 'string';
|
|
117
|
+
case 'integer':
|
|
118
|
+
case 'int':
|
|
119
|
+
return typeof value === 'number' && Number.isInteger(value);
|
|
120
|
+
case 'decimal':
|
|
121
|
+
return typeof value === 'number';
|
|
122
|
+
case 'boolean':
|
|
123
|
+
return typeof value === 'boolean';
|
|
124
|
+
case 'float':
|
|
125
|
+
case 'double':
|
|
126
|
+
return typeof value === 'number';
|
|
127
|
+
default:
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
getValueTypeName(value) {
|
|
132
|
+
if (typeof value === 'string') {
|
|
133
|
+
return 'string';
|
|
134
|
+
}
|
|
135
|
+
else if (typeof value === 'number') {
|
|
136
|
+
return Number.isInteger(value) ? 'integer' : 'decimal';
|
|
137
|
+
}
|
|
138
|
+
else if (typeof value === 'boolean') {
|
|
139
|
+
return 'boolean';
|
|
140
|
+
}
|
|
141
|
+
else if (Array.isArray(value)) {
|
|
142
|
+
return 'array';
|
|
143
|
+
}
|
|
144
|
+
else if (typeof value === 'object' && value !== null) {
|
|
145
|
+
return 'object';
|
|
146
|
+
}
|
|
147
|
+
else if (value === null) {
|
|
148
|
+
return 'null';
|
|
149
|
+
}
|
|
150
|
+
else if (value === undefined) {
|
|
151
|
+
return 'undefined';
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
return typeof value;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
getPropertyValue(dict, propertyName) {
|
|
158
|
+
if (propertyName in dict) {
|
|
159
|
+
const value = dict[propertyName];
|
|
160
|
+
if (Array.isArray(value)) {
|
|
161
|
+
const items = [];
|
|
162
|
+
for (const item of value) {
|
|
163
|
+
items.push(item?.toString() ?? '');
|
|
164
|
+
}
|
|
165
|
+
return items.length > 0 ? items[0] : undefined;
|
|
166
|
+
}
|
|
167
|
+
return value?.toString();
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import type { ICanonDocumentRepository } from '@canon-protocol/types/document/models';
|
|
3
|
+
import type { IRepositoryValidationRule } from './IRepositoryValidationRule.js';
|
|
4
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
5
|
+
export declare class SubClassOfReferenceRule implements IRepositoryValidationRule {
|
|
6
|
+
private readonly builtInClasses;
|
|
7
|
+
get ruleName(): string;
|
|
8
|
+
validateAsync(document: CanonDocument, repository: ICanonDocumentRepository): Promise<OntologyValidationError[]>;
|
|
9
|
+
private isClassAvailableInImports;
|
|
10
|
+
private isClassAvailableInImportsRecursive;
|
|
11
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
2
|
+
import { ValidationSeverity } from '../../ValidationSeverity.js';
|
|
3
|
+
import { normalizeToStringList } from '../normalizeToStringList.js';
|
|
4
|
+
export class SubClassOfReferenceRule {
|
|
5
|
+
builtInClasses = new Set([
|
|
6
|
+
'Resource',
|
|
7
|
+
'Class',
|
|
8
|
+
'Literal',
|
|
9
|
+
'Thing',
|
|
10
|
+
'Nothing',
|
|
11
|
+
'Property',
|
|
12
|
+
'DatatypeProperty',
|
|
13
|
+
'ObjectProperty',
|
|
14
|
+
'AnnotationProperty',
|
|
15
|
+
'FunctionalProperty',
|
|
16
|
+
'InverseFunctionalProperty',
|
|
17
|
+
'TransitiveProperty',
|
|
18
|
+
'SymmetricProperty'
|
|
19
|
+
]);
|
|
20
|
+
get ruleName() {
|
|
21
|
+
return 'SubClassOfReference';
|
|
22
|
+
}
|
|
23
|
+
async validateAsync(document, repository) {
|
|
24
|
+
const errors = [];
|
|
25
|
+
const definedClasses = new Set();
|
|
26
|
+
for (const [entityName, entityDef] of Object.entries(document.body)) {
|
|
27
|
+
if (typeof entityDef === 'object' && entityDef !== null && !Array.isArray(entityDef)) {
|
|
28
|
+
const entity = entityDef;
|
|
29
|
+
const type = entity['type']?.toString();
|
|
30
|
+
if (type === 'Class' || (type && type.endsWith('.Class'))) {
|
|
31
|
+
definedClasses.add(entityName);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const [entityName, entityDef] of Object.entries(document.body)) {
|
|
36
|
+
if (typeof entityDef === 'object' && entityDef !== null && !Array.isArray(entityDef)) {
|
|
37
|
+
const entity = entityDef;
|
|
38
|
+
const parentClasses = normalizeToStringList(entity['subClassOf']);
|
|
39
|
+
for (let i = 0; i < parentClasses.length; i++) {
|
|
40
|
+
const parentClass = parentClasses[i];
|
|
41
|
+
if (this.builtInClasses.has(parentClass)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (definedClasses.has(parentClass)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const isAvailable = await this.isClassAvailableInImports(document, repository, parentClass);
|
|
48
|
+
if (!isAvailable) {
|
|
49
|
+
const indexSuffix = parentClasses.length > 1 ? `[${i}]` : '';
|
|
50
|
+
const error = new OntologyValidationError();
|
|
51
|
+
error.ruleType = this.ruleName;
|
|
52
|
+
error.severity = ValidationSeverity.Error;
|
|
53
|
+
error.message = `Class '${parentClass}' referenced in subClassOf${indexSuffix} is not defined or imported`;
|
|
54
|
+
error.suggestion = `Define '${parentClass}' as a Class, or import a namespace that contains it`;
|
|
55
|
+
error.entityName = entityName;
|
|
56
|
+
error.propertyName = 'subClassOf';
|
|
57
|
+
error.actualValue = parentClass;
|
|
58
|
+
error.expectedValue = 'Defined or imported class';
|
|
59
|
+
errors.push(error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return errors;
|
|
65
|
+
}
|
|
66
|
+
async isClassAvailableInImports(document, repository, className) {
|
|
67
|
+
if (!document.metadata.imports) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
let namespaceAlias = null;
|
|
71
|
+
let actualClassName = className;
|
|
72
|
+
if (className.includes('.')) {
|
|
73
|
+
const parts = className.split('.', 2);
|
|
74
|
+
namespaceAlias = parts[0];
|
|
75
|
+
actualClassName = parts[1];
|
|
76
|
+
}
|
|
77
|
+
for (const [publisher, imports] of Object.entries(document.metadata.imports)) {
|
|
78
|
+
for (const import_ of imports) {
|
|
79
|
+
if (namespaceAlias !== null && import_.alias !== namespaceAlias) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const importedDoc = await repository.getHighestCompatibleVersionAsync(publisher, import_);
|
|
83
|
+
if (importedDoc) {
|
|
84
|
+
const lookupName = namespaceAlias !== null ? actualClassName : className;
|
|
85
|
+
const entityDef = importedDoc.body[lookupName];
|
|
86
|
+
if (entityDef && typeof entityDef === 'object' && !Array.isArray(entityDef)) {
|
|
87
|
+
const entity = entityDef;
|
|
88
|
+
const type = entity['type']?.toString();
|
|
89
|
+
if (type === 'Class' || (type && type.endsWith('.Class'))) {
|
|
90
|
+
return true; // Found the class definition
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const visitedDocs = new Set();
|
|
94
|
+
if (await this.isClassAvailableInImportsRecursive(importedDoc, repository, lookupName, visitedDocs)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
async isClassAvailableInImportsRecursive(document, repository, className, visitedDocs) {
|
|
103
|
+
const docId = document.metadata.namespace_?.toString() ?? '';
|
|
104
|
+
if (docId && visitedDocs.has(docId)) {
|
|
105
|
+
return false; // Already checked this document
|
|
106
|
+
}
|
|
107
|
+
if (docId) {
|
|
108
|
+
visitedDocs.add(docId);
|
|
109
|
+
}
|
|
110
|
+
if (!document.metadata.imports) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
for (const [publisher, imports] of Object.entries(document.metadata.imports)) {
|
|
114
|
+
for (const import_ of imports) {
|
|
115
|
+
const importedDoc = await repository.getHighestCompatibleVersionAsync(publisher, import_);
|
|
116
|
+
if (importedDoc) {
|
|
117
|
+
const entityDef = importedDoc.body[className];
|
|
118
|
+
if (entityDef && typeof entityDef === 'object' && !Array.isArray(entityDef)) {
|
|
119
|
+
const entity = entityDef;
|
|
120
|
+
const type = entity['type']?.toString();
|
|
121
|
+
if (type === 'Class' || (type && type.endsWith('.Class'))) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (await this.isClassAvailableInImportsRecursive(importedDoc, repository, className, visitedDocs)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CanonDocument } from '@canon-protocol/types/document/models/types';
|
|
2
|
+
import type { ICanonDocumentRepository } from '@canon-protocol/types/document/models';
|
|
3
|
+
import type { IRepositoryValidationRule } from './IRepositoryValidationRule.js';
|
|
4
|
+
import { OntologyValidationError } from '../../OntologyValidationError.js';
|
|
5
|
+
export declare class SubPropertyOfReferenceRule implements IRepositoryValidationRule {
|
|
6
|
+
private readonly builtInProperties;
|
|
7
|
+
get ruleName(): string;
|
|
8
|
+
validateAsync(document: CanonDocument, repository: ICanonDocumentRepository): Promise<OntologyValidationError[]>;
|
|
9
|
+
private isPropertyAvailableInImports;
|
|
10
|
+
private isPropertyAvailableInImportsRecursive;
|
|
11
|
+
}
|