@api-client/core 0.18.13 → 0.18.15
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/build/src/modeling/DomainValidation.d.ts +78 -0
- package/build/src/modeling/DomainValidation.d.ts.map +1 -1
- package/build/src/modeling/DomainValidation.js +78 -0
- package/build/src/modeling/DomainValidation.js.map +1 -1
- package/build/src/modeling/importers/FilteringJsonSchemaImporter.d.ts +84 -0
- package/build/src/modeling/importers/FilteringJsonSchemaImporter.d.ts.map +1 -0
- package/build/src/modeling/importers/FilteringJsonSchemaImporter.js +254 -0
- package/build/src/modeling/importers/FilteringJsonSchemaImporter.js.map +1 -0
- package/build/src/modeling/importers/SchemaFilteringStrategy.d.ts +72 -0
- package/build/src/modeling/importers/SchemaFilteringStrategy.d.ts.map +1 -0
- package/build/src/modeling/importers/SchemaFilteringStrategy.js +140 -0
- package/build/src/modeling/importers/SchemaFilteringStrategy.js.map +1 -0
- package/build/src/modeling/validation/entity_validation.d.ts +17 -3
- package/build/src/modeling/validation/entity_validation.d.ts.map +1 -1
- package/build/src/modeling/validation/entity_validation.js +51 -10
- package/build/src/modeling/validation/entity_validation.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/data/models/example-generator-api.json +15 -15
- package/package.json +1 -1
- package/src/modeling/DomainValidation.ts +78 -0
- package/src/modeling/importers/FilteringJsonSchemaImporter.ts +324 -0
- package/src/modeling/importers/SchemaFilteringStrategy.ts +201 -0
- package/src/modeling/validation/entity_validation.ts +59 -10
- package/tests/unit/modeling/importers/schema_filtering.spec.ts +764 -0
- package/tests/unit/modeling/validation/entity_validation.spec.ts +95 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for schema filtering during import.
|
|
4
|
+
*/
|
|
5
|
+
export interface SchemaFilteringOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Whether to exclude schemas that are empty objects with no properties.
|
|
8
|
+
* Default: false
|
|
9
|
+
*/
|
|
10
|
+
excludeEmptyObjects?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Whether to exclude schemas that only serve as conceptual markers.
|
|
13
|
+
* These are typically schemas with only type: "object" and description
|
|
14
|
+
* but no properties, constraints, or meaningful structure.
|
|
15
|
+
* Default: false
|
|
16
|
+
*/
|
|
17
|
+
excludeConceptualMarkers?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Custom predicate function to determine if a schema should be excluded.
|
|
20
|
+
* Return true to exclude the schema from import.
|
|
21
|
+
*/
|
|
22
|
+
customExclusionFilter?: (schema: JSONSchema7, schemaId?: string) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* List of schema IDs to explicitly exclude.
|
|
25
|
+
*/
|
|
26
|
+
excludeSchemaIds?: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Patterns to match against schema titles or IDs for exclusion.
|
|
29
|
+
*/
|
|
30
|
+
excludePatterns?: RegExp[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Strategy class for determining which schemas should be filtered out during import.
|
|
34
|
+
*/
|
|
35
|
+
export declare class SchemaFilteringStrategy {
|
|
36
|
+
private options;
|
|
37
|
+
constructor(options?: SchemaFilteringOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Determines whether a schema should be excluded from import.
|
|
40
|
+
*/
|
|
41
|
+
shouldExcludeSchema(schema: JSONSchema7, schemaId?: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a schema represents an empty object with no meaningful structure.
|
|
44
|
+
*/
|
|
45
|
+
private isEmptyObjectSchema;
|
|
46
|
+
/**
|
|
47
|
+
* Checks if a schema is likely a conceptual marker with no structural value.
|
|
48
|
+
*/
|
|
49
|
+
private isConceptualMarkerSchema;
|
|
50
|
+
/**
|
|
51
|
+
* Gets a human-readable reason why a schema was excluded.
|
|
52
|
+
*/
|
|
53
|
+
getExclusionReason(schema: JSONSchema7, schemaId?: string): string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Predefined filtering strategies for common use cases.
|
|
57
|
+
*/
|
|
58
|
+
export declare const FILTERING_STRATEGIES: {
|
|
59
|
+
/**
|
|
60
|
+
* Strategy for API modeling - excludes schemas that don't contribute to API structure.
|
|
61
|
+
*/
|
|
62
|
+
readonly API_MODELING: SchemaFilteringStrategy;
|
|
63
|
+
/**
|
|
64
|
+
* Strategy for database modeling - very strict, only includes schemas with concrete properties.
|
|
65
|
+
*/
|
|
66
|
+
readonly DATABASE_MODELING: SchemaFilteringStrategy;
|
|
67
|
+
/**
|
|
68
|
+
* Conservative strategy - only excludes obviously empty schemas.
|
|
69
|
+
*/
|
|
70
|
+
readonly CONSERVATIVE: SchemaFilteringStrategy;
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=SchemaFilteringStrategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SchemaFilteringStrategy.d.ts","sourceRoot":"","sources":["../../../../src/modeling/importers/SchemaFilteringStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAE7B;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAElC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAE3E;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAE3B;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED;;GAEG;AACH,qBAAa,uBAAuB;IACtB,OAAO,CAAC,OAAO;gBAAP,OAAO,GAAE,sBAA2B;IAExD;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO;IAiCpE;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAkB3B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAoChC;;OAEG;IACH,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;CAenE;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB;IAC/B;;OAEG;;IAUH;;OAEG;;IAiBH;;OAEG;;CAIK,CAAA"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy class for determining which schemas should be filtered out during import.
|
|
3
|
+
*/
|
|
4
|
+
export class SchemaFilteringStrategy {
|
|
5
|
+
options;
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Determines whether a schema should be excluded from import.
|
|
11
|
+
*/
|
|
12
|
+
shouldExcludeSchema(schema, schemaId) {
|
|
13
|
+
// Check explicit exclusion list
|
|
14
|
+
if (schemaId && this.options.excludeSchemaIds?.includes(schemaId)) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
// Check pattern exclusions
|
|
18
|
+
if (schemaId && this.options.excludePatterns) {
|
|
19
|
+
for (const pattern of this.options.excludePatterns) {
|
|
20
|
+
if (pattern.test(schemaId) || (schema.title && pattern.test(schema.title))) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Check for empty objects
|
|
26
|
+
if (this.options.excludeEmptyObjects && this.isEmptyObjectSchema(schema)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
// Check for conceptual markers
|
|
30
|
+
if (this.options.excludeConceptualMarkers && this.isConceptualMarkerSchema(schema)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
// Apply custom filter
|
|
34
|
+
if (this.options.customExclusionFilter?.(schema, schemaId)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Checks if a schema represents an empty object with no meaningful structure.
|
|
41
|
+
*/
|
|
42
|
+
isEmptyObjectSchema(schema) {
|
|
43
|
+
return (schema.type === 'object' &&
|
|
44
|
+
(!schema.properties || Object.keys(schema.properties).length === 0) &&
|
|
45
|
+
!schema.additionalProperties &&
|
|
46
|
+
!schema.patternProperties &&
|
|
47
|
+
!schema.allOf &&
|
|
48
|
+
!schema.oneOf &&
|
|
49
|
+
!schema.anyOf &&
|
|
50
|
+
!schema.if &&
|
|
51
|
+
!schema.then &&
|
|
52
|
+
!schema.else &&
|
|
53
|
+
!schema.required?.length &&
|
|
54
|
+
!schema.minProperties &&
|
|
55
|
+
!schema.maxProperties);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Checks if a schema is likely a conceptual marker with no structural value.
|
|
59
|
+
*/
|
|
60
|
+
isConceptualMarkerSchema(schema) {
|
|
61
|
+
// Check for schemas that are just empty objects or only have allOf with references
|
|
62
|
+
if (this.isEmptyObjectSchema(schema)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
// Check for schemas that only contain a single allOf with a $ref (like ActionStatusType)
|
|
66
|
+
if (schema.type === 'object' &&
|
|
67
|
+
schema.allOf?.length === 1 &&
|
|
68
|
+
typeof schema.allOf[0] === 'object' &&
|
|
69
|
+
'$ref' in schema.allOf[0] &&
|
|
70
|
+
!schema.properties &&
|
|
71
|
+
!schema.additionalProperties) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
// Check for schemas that only have description and type but no structure
|
|
75
|
+
if (schema.type === 'object' &&
|
|
76
|
+
schema.description &&
|
|
77
|
+
!schema.properties &&
|
|
78
|
+
!schema.additionalProperties &&
|
|
79
|
+
!schema.allOf &&
|
|
80
|
+
!schema.oneOf &&
|
|
81
|
+
!schema.anyOf &&
|
|
82
|
+
Object.keys(schema).filter((key) => !['$schema', '$id', 'title', 'description', 'type'].includes(key)).length ===
|
|
83
|
+
0) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Gets a human-readable reason why a schema was excluded.
|
|
90
|
+
*/
|
|
91
|
+
getExclusionReason(schema, schemaId) {
|
|
92
|
+
if (schemaId && this.options.excludeSchemaIds?.includes(schemaId)) {
|
|
93
|
+
return `Schema ID '${schemaId}' is in the exclusion list`;
|
|
94
|
+
}
|
|
95
|
+
if (this.isEmptyObjectSchema(schema)) {
|
|
96
|
+
return 'Schema is an empty object with no structural definition';
|
|
97
|
+
}
|
|
98
|
+
if (this.isConceptualMarkerSchema(schema)) {
|
|
99
|
+
return 'Schema appears to be a conceptual marker with no concrete structure';
|
|
100
|
+
}
|
|
101
|
+
return 'Schema excluded by custom filter';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Predefined filtering strategies for common use cases.
|
|
106
|
+
*/
|
|
107
|
+
export const FILTERING_STRATEGIES = {
|
|
108
|
+
/**
|
|
109
|
+
* Strategy for API modeling - excludes schemas that don't contribute to API structure.
|
|
110
|
+
*/
|
|
111
|
+
API_MODELING: new SchemaFilteringStrategy({
|
|
112
|
+
excludeEmptyObjects: true,
|
|
113
|
+
excludeConceptualMarkers: true,
|
|
114
|
+
excludePatterns: [
|
|
115
|
+
/.*Enumeration$/, // Exclude *Enumeration schemas
|
|
116
|
+
/.*Type$/, // Exclude conceptual *Type schemas that are just markers
|
|
117
|
+
],
|
|
118
|
+
}),
|
|
119
|
+
/**
|
|
120
|
+
* Strategy for database modeling - very strict, only includes schemas with concrete properties.
|
|
121
|
+
*/
|
|
122
|
+
DATABASE_MODELING: new SchemaFilteringStrategy({
|
|
123
|
+
excludeEmptyObjects: true,
|
|
124
|
+
excludeConceptualMarkers: true,
|
|
125
|
+
customExclusionFilter: (schema) => {
|
|
126
|
+
// For database modeling, exclude anything that doesn't have properties or clear structure
|
|
127
|
+
return (schema.type === 'object' &&
|
|
128
|
+
!schema.properties &&
|
|
129
|
+
!schema.additionalProperties &&
|
|
130
|
+
!schema.allOf?.some((subSchema) => typeof subSchema === 'object' && 'properties' in subSchema && subSchema.properties));
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
/**
|
|
134
|
+
* Conservative strategy - only excludes obviously empty schemas.
|
|
135
|
+
*/
|
|
136
|
+
CONSERVATIVE: new SchemaFilteringStrategy({
|
|
137
|
+
excludeEmptyObjects: true,
|
|
138
|
+
}),
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=SchemaFilteringStrategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SchemaFilteringStrategy.js","sourceRoot":"","sources":["../../../../src/modeling/importers/SchemaFilteringStrategy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JSONSchema7 } from 'json-schema'\n\n/**\n * Configuration options for schema filtering during import.\n */\nexport interface SchemaFilteringOptions {\n /**\n * Whether to exclude schemas that are empty objects with no properties.\n * Default: false\n */\n excludeEmptyObjects?: boolean\n\n /**\n * Whether to exclude schemas that only serve as conceptual markers.\n * These are typically schemas with only type: \"object\" and description\n * but no properties, constraints, or meaningful structure.\n * Default: false\n */\n excludeConceptualMarkers?: boolean\n\n /**\n * Custom predicate function to determine if a schema should be excluded.\n * Return true to exclude the schema from import.\n */\n customExclusionFilter?: (schema: JSONSchema7, schemaId?: string) => boolean\n\n /**\n * List of schema IDs to explicitly exclude.\n */\n excludeSchemaIds?: string[]\n\n /**\n * Patterns to match against schema titles or IDs for exclusion.\n */\n excludePatterns?: RegExp[]\n}\n\n/**\n * Strategy class for determining which schemas should be filtered out during import.\n */\nexport class SchemaFilteringStrategy {\n constructor(private options: SchemaFilteringOptions = {}) {}\n\n /**\n * Determines whether a schema should be excluded from import.\n */\n shouldExcludeSchema(schema: JSONSchema7, schemaId?: string): boolean {\n // Check explicit exclusion list\n if (schemaId && this.options.excludeSchemaIds?.includes(schemaId)) {\n return true\n }\n\n // Check pattern exclusions\n if (schemaId && this.options.excludePatterns) {\n for (const pattern of this.options.excludePatterns) {\n if (pattern.test(schemaId) || (schema.title && pattern.test(schema.title))) {\n return true\n }\n }\n }\n\n // Check for empty objects\n if (this.options.excludeEmptyObjects && this.isEmptyObjectSchema(schema)) {\n return true\n }\n\n // Check for conceptual markers\n if (this.options.excludeConceptualMarkers && this.isConceptualMarkerSchema(schema)) {\n return true\n }\n\n // Apply custom filter\n if (this.options.customExclusionFilter?.(schema, schemaId)) {\n return true\n }\n\n return false\n }\n\n /**\n * Checks if a schema represents an empty object with no meaningful structure.\n */\n private isEmptyObjectSchema(schema: JSONSchema7): boolean {\n return (\n schema.type === 'object' &&\n (!schema.properties || Object.keys(schema.properties).length === 0) &&\n !schema.additionalProperties &&\n !schema.patternProperties &&\n !schema.allOf &&\n !schema.oneOf &&\n !schema.anyOf &&\n !schema.if &&\n !schema.then &&\n !schema.else &&\n !schema.required?.length &&\n !schema.minProperties &&\n !schema.maxProperties\n )\n }\n\n /**\n * Checks if a schema is likely a conceptual marker with no structural value.\n */\n private isConceptualMarkerSchema(schema: JSONSchema7): boolean {\n // Check for schemas that are just empty objects or only have allOf with references\n if (this.isEmptyObjectSchema(schema)) {\n return true\n }\n\n // Check for schemas that only contain a single allOf with a $ref (like ActionStatusType)\n if (\n schema.type === 'object' &&\n schema.allOf?.length === 1 &&\n typeof schema.allOf[0] === 'object' &&\n '$ref' in schema.allOf[0] &&\n !schema.properties &&\n !schema.additionalProperties\n ) {\n return true\n }\n\n // Check for schemas that only have description and type but no structure\n if (\n schema.type === 'object' &&\n schema.description &&\n !schema.properties &&\n !schema.additionalProperties &&\n !schema.allOf &&\n !schema.oneOf &&\n !schema.anyOf &&\n Object.keys(schema).filter((key) => !['$schema', '$id', 'title', 'description', 'type'].includes(key)).length ===\n 0\n ) {\n return true\n }\n\n return false\n }\n\n /**\n * Gets a human-readable reason why a schema was excluded.\n */\n getExclusionReason(schema: JSONSchema7, schemaId?: string): string {\n if (schemaId && this.options.excludeSchemaIds?.includes(schemaId)) {\n return `Schema ID '${schemaId}' is in the exclusion list`\n }\n\n if (this.isEmptyObjectSchema(schema)) {\n return 'Schema is an empty object with no structural definition'\n }\n\n if (this.isConceptualMarkerSchema(schema)) {\n return 'Schema appears to be a conceptual marker with no concrete structure'\n }\n\n return 'Schema excluded by custom filter'\n }\n}\n\n/**\n * Predefined filtering strategies for common use cases.\n */\nexport const FILTERING_STRATEGIES = {\n /**\n * Strategy for API modeling - excludes schemas that don't contribute to API structure.\n */\n API_MODELING: new SchemaFilteringStrategy({\n excludeEmptyObjects: true,\n excludeConceptualMarkers: true,\n excludePatterns: [\n /.*Enumeration$/, // Exclude *Enumeration schemas\n /.*Type$/, // Exclude conceptual *Type schemas that are just markers\n ],\n }),\n\n /**\n * Strategy for database modeling - very strict, only includes schemas with concrete properties.\n */\n DATABASE_MODELING: new SchemaFilteringStrategy({\n excludeEmptyObjects: true,\n excludeConceptualMarkers: true,\n customExclusionFilter: (schema: JSONSchema7) => {\n // For database modeling, exclude anything that doesn't have properties or clear structure\n return (\n schema.type === 'object' &&\n !schema.properties &&\n !schema.additionalProperties &&\n !schema.allOf?.some(\n (subSchema) => typeof subSchema === 'object' && 'properties' in subSchema && subSchema.properties\n )\n )\n },\n }),\n\n /**\n * Conservative strategy - only excludes obviously empty schemas.\n */\n CONSERVATIVE: new SchemaFilteringStrategy({\n excludeEmptyObjects: true,\n }),\n} as const\n"]}
|
|
@@ -14,15 +14,29 @@ export declare class EntityValidation {
|
|
|
14
14
|
/**
|
|
15
15
|
* Performs all the validation rules on the entity.
|
|
16
16
|
* If you are interested in a specific rule, use the specific method.
|
|
17
|
-
* @param target The target entity to validate. Can be a string with
|
|
17
|
+
* @param target The target entity to validate. Can be a string with
|
|
18
|
+
* the entity key or a DomainEntity object.
|
|
18
19
|
*/
|
|
19
20
|
validate(target: string | DomainEntity): DomainValidation[];
|
|
20
21
|
/**
|
|
21
|
-
* Validates the entity
|
|
22
|
+
* Validates the entity primary key.
|
|
22
23
|
* @param entity The entity to validate
|
|
23
|
-
* @returns The list of validation messages.
|
|
24
24
|
*/
|
|
25
25
|
validatePrimaryKey(entity: DomainEntity): DomainValidation[];
|
|
26
|
+
/**
|
|
27
|
+
* Checks if an entity has properties through its entire inheritance chain.
|
|
28
|
+
* This includes direct properties and properties inherited from any level of parent entities.
|
|
29
|
+
* @param entity The entity to check
|
|
30
|
+
* @returns True if the entity has properties either directly or through inheritance
|
|
31
|
+
*/
|
|
32
|
+
private hasPropertiesInherited;
|
|
33
|
+
/**
|
|
34
|
+
* Checks if an entity has associations through its entire inheritance chain.
|
|
35
|
+
* This includes direct associations and associations inherited from any level of parent entities.
|
|
36
|
+
* @param entity The entity to check
|
|
37
|
+
* @returns True if the entity has associations either directly or through inheritance
|
|
38
|
+
*/
|
|
39
|
+
private hasAssociationsInherited;
|
|
26
40
|
/**
|
|
27
41
|
* Checks if the entity has the minimum required properties.
|
|
28
42
|
* @param entity The entity to validate
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity_validation.d.ts","sourceRoot":"","sources":["../../../../src/modeling/validation/entity_validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD;;;;;;GAMG;AACH,qBAAa,gBAAgB;IACf,SAAS,CAAC,MAAM,EAAE,UAAU;gBAAlB,MAAM,EAAE,UAAU;IAExC
|
|
1
|
+
{"version":3,"file":"entity_validation.d.ts","sourceRoot":"","sources":["../../../../src/modeling/validation/entity_validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD;;;;;;GAMG;AACH,qBAAa,gBAAgB;IACf,SAAS,CAAC,MAAM,EAAE,UAAU;gBAAlB,MAAM,EAAE,UAAU;IAExC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,gBAAgB,EAAE;IAiC3D;;;OAGG;IACH,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,EAAE;IAmB5D;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAgB9B;;;;;OAKG;IACH,OAAO,CAAC,wBAAwB;IAgBhC;;;OAGG;IACH,yBAAyB,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,EAAE;IAuBnE;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,EAAE;IAuFtD;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,EAAE;CAwCrD"}
|
|
@@ -15,7 +15,8 @@ export class EntityValidation {
|
|
|
15
15
|
/**
|
|
16
16
|
* Performs all the validation rules on the entity.
|
|
17
17
|
* If you are interested in a specific rule, use the specific method.
|
|
18
|
-
* @param target The target entity to validate. Can be a string with
|
|
18
|
+
* @param target The target entity to validate. Can be a string with
|
|
19
|
+
* the entity key or a DomainEntity object.
|
|
19
20
|
*/
|
|
20
21
|
validate(target) {
|
|
21
22
|
const results = [];
|
|
@@ -40,20 +41,19 @@ export class EntityValidation {
|
|
|
40
41
|
});
|
|
41
42
|
return results;
|
|
42
43
|
}
|
|
43
|
-
const
|
|
44
|
-
results.push(...
|
|
45
|
-
const
|
|
46
|
-
results.push(...
|
|
44
|
+
const primaryKey = this.validatePrimaryKey(entity);
|
|
45
|
+
results.push(...primaryKey);
|
|
46
|
+
const minimumProperties = this.minimumRequiredProperties(entity);
|
|
47
|
+
results.push(...minimumProperties);
|
|
47
48
|
const name = this.validateName(entity);
|
|
48
49
|
results.push(...name);
|
|
49
|
-
const
|
|
50
|
-
results.push(...
|
|
50
|
+
const unique = this.uniqueName(entity);
|
|
51
|
+
results.push(...unique);
|
|
51
52
|
return results;
|
|
52
53
|
}
|
|
53
54
|
/**
|
|
54
|
-
* Validates the entity
|
|
55
|
+
* Validates the entity primary key.
|
|
55
56
|
* @param entity The entity to validate
|
|
56
|
-
* @returns The list of validation messages.
|
|
57
57
|
*/
|
|
58
58
|
validatePrimaryKey(entity) {
|
|
59
59
|
const results = [];
|
|
@@ -73,13 +73,54 @@ export class EntityValidation {
|
|
|
73
73
|
}
|
|
74
74
|
return results;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Checks if an entity has properties through its entire inheritance chain.
|
|
78
|
+
* This includes direct properties and properties inherited from any level of parent entities.
|
|
79
|
+
* @param entity The entity to check
|
|
80
|
+
* @returns True if the entity has properties either directly or through inheritance
|
|
81
|
+
*/
|
|
82
|
+
hasPropertiesInherited(entity) {
|
|
83
|
+
// Check direct properties first
|
|
84
|
+
if (entity.hasProperties()) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
// Check all parents recursively
|
|
88
|
+
for (const parent of entity.listParents()) {
|
|
89
|
+
if (this.hasPropertiesInherited(parent)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Checks if an entity has associations through its entire inheritance chain.
|
|
97
|
+
* This includes direct associations and associations inherited from any level of parent entities.
|
|
98
|
+
* @param entity The entity to check
|
|
99
|
+
* @returns True if the entity has associations either directly or through inheritance
|
|
100
|
+
*/
|
|
101
|
+
hasAssociationsInherited(entity) {
|
|
102
|
+
// Check direct associations first
|
|
103
|
+
if (entity.hasAssociations()) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
// Check all parents recursively
|
|
107
|
+
for (const parent of entity.listParents()) {
|
|
108
|
+
if (this.hasAssociationsInherited(parent)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
76
114
|
/**
|
|
77
115
|
* Checks if the entity has the minimum required properties.
|
|
78
116
|
* @param entity The entity to validate
|
|
79
117
|
*/
|
|
80
118
|
minimumRequiredProperties(entity) {
|
|
81
119
|
const results = [];
|
|
82
|
-
if
|
|
120
|
+
// Check if entity has properties or associations through entire inheritance chain
|
|
121
|
+
const hasProperties = this.hasPropertiesInherited(entity);
|
|
122
|
+
const hasAssociations = this.hasAssociationsInherited(entity);
|
|
123
|
+
if (!hasProperties && !hasAssociations) {
|
|
83
124
|
const message = `The "${entity.info.getLabel()}" entity has no properties. It will be ignored.`;
|
|
84
125
|
const help = `Entities that have no properties are ignored in the data domain. No schema will be generated for it.`;
|
|
85
126
|
results.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity_validation.js","sourceRoot":"","sources":["../../../../src/modeling/validation/entity_validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { DomainEntityKind } from '../../models/kinds.js'\nimport type { DataDomain } from '../DataDomain.js'\nimport type { DomainEntity } from '../DomainEntity.js'\nimport { ReservedKeywords } from './postgresql.js'\nimport type { DomainValidation } from './rules.js'\n\n/**\n * EntityValidation is a class that performs validation on entities in a data domain.\n *\n * @remarks\n * - We do not need to check for parent uniqueness here, because in the graph there can only be one edge\n * between two nodes. Parent relationships are described by an edge in the graph.\n */\nexport class EntityValidation {\n constructor(protected domain: DataDomain) {}\n\n /**\n * Performs all the validation rules on the entity.\n * If you are interested in a specific rule, use the specific method.\n * @param target The target entity to validate. Can be a string with the entity key or a DomainEntity object.\n */\n validate(target: string | DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n let entity: DomainEntity | undefined\n if (typeof target === 'string') {\n entity = this.domain.findEntity(target)\n } else {\n entity = target\n }\n if (!entity) {\n const message = `The \"${target}\" entity does not exist.`\n const help = `The entity must be defined in the domain.`\n results.push({\n field: '*',\n rule: 'exists',\n message,\n help,\n key: target as string,\n kind: DomainEntityKind,\n severity: 'error',\n })\n return results\n }\n const primary = this.validatePrimaryKey(entity)\n results.push(...primary)\n const minimum = this.minimumRequiredProperties(entity)\n results.push(...minimum)\n const name = this.validateName(entity)\n results.push(...name)\n const uniqueName = this.uniqueName(entity)\n results.push(...uniqueName)\n return results\n }\n\n /**\n * Validates the entity against the primary key validation rules.\n * @param entity The entity to validate\n * @returns The list of validation messages.\n */\n validatePrimaryKey(entity: DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n const primary = entity.primaryKey()\n if (!primary) {\n const message = `The \"${entity.info.getLabel()}\" entity has no identifier.`\n const help = `An entity that can exists by itself must have identifier defined.`\n results.push({\n field: 'properties',\n rule: 'primary_key',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n return results\n }\n\n /**\n * Checks if the entity has the minimum required properties.\n * @param entity The entity to validate\n */\n minimumRequiredProperties(entity: DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n if (!entity.hasProperties() && !entity.hasAssociations()) {\n const message = `The \"${entity.info.getLabel()}\" entity has no properties. It will be ignored.`\n const help = `Entities that have no properties are ignored in the data domain. No schema will be generated for it.`\n results.push({\n field: 'properties',\n rule: 'required',\n message,\n help,\n severity: 'warning',\n key: entity.key,\n kind: entity.kind,\n })\n }\n return results\n }\n\n /**\n * Validates the entity name.\n *\n * @remarks\n * - An entity must have a name defined.\n * - The name has to follow the same rules as the names in a PostgreSQL database.\n * - Table names must start with a letter (a-z) or underscore (_).\n * - Subsequent characters can be letters, digits (0-9), or underscores (_).\n * - Names are case-insensitive.\n * - The maximum length of a table name is 31 characters\n * - Should be snake case (it's a convention, not an error).\n * - Should not be a reserved word (for example: \"IN\", \"SELECT\", \"FROM\", etc.).\n * @param entity The entity to validate\n */\n validateName(entity: DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n const label = entity.info.getLabel()\n if (!entity.info.name) {\n const message = `The \"${label}\" entity has no name.`\n const help = `An entity must have a name.`\n results.push({\n field: 'name',\n rule: 'required',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n return results\n }\n\n const name = entity.info.name\n if (name.length < 2) {\n const message = `The \"${label}\" entity name is too short.`\n const help = `The name must be at least 2 characters long.`\n results.push({\n field: 'name',\n rule: 'length',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n if (name.length > 31) {\n const message = `The \"${label}\" entity name is too long.`\n const help = `The name must be at most 31 characters long.`\n results.push({\n field: 'name',\n rule: 'length',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n const message = `The \"${label}\" entity name is invalid.`\n const help = `The name must start with a letter (a-z) or underscore (_). Subsequent characters can be letters, digits (0-9), or underscores (_).`\n results.push({\n field: 'name',\n rule: 'format',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n if (/^[A-Z]/.test(name)) {\n const message = `The \"${label}\" entity name is not in snake case.`\n const help = `The name should be in snake case (lowercase letters and underscores).`\n results.push({\n field: 'name',\n rule: 'snake_case',\n message,\n help,\n severity: 'info',\n key: entity.key,\n kind: entity.kind,\n })\n }\n if (ReservedKeywords.has(name.toUpperCase())) {\n const message = `The \"${label}\" entity name is a reserved keyword.`\n const help = `The name should not be a reserved keyword.`\n results.push({\n field: 'name',\n rule: 'reserved',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n return results\n }\n\n /**\n * Checks if the entity name is unique in the data domain.\n * @param entity The entity to validate\n */\n uniqueName(entity: DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n const name = entity.info.name?.toLowerCase()\n if (!name) {\n return results\n }\n // We need to check the unique names in all entities, including foreign ones.\n for (const other of this.domain.listEntities()) {\n if (other.info.name?.toLowerCase() === name && other.key !== entity.key) {\n const message = `The \"${name}\" entity name is already used in the data domain.`\n const help = `The name must be unique. This includes references to other data domains.`\n results.push({\n field: 'name',\n rule: 'unique',\n message,\n help,\n severity: 'error',\n key: other.key,\n kind: other.kind,\n })\n }\n }\n for (const other of this.domain.listAllForeignEntities()) {\n if (other.info.name?.toLowerCase() === name && other.key !== entity.key) {\n const message = `The \"${name}\" entity name is already used in the foreign data domain.`\n const help = `The name must be unique. This includes references to other data domains.`\n results.push({\n field: 'name',\n rule: 'unique',\n message,\n help,\n severity: 'error',\n key: other.key,\n kind: other.kind,\n parent: other.domain.key,\n })\n }\n }\n return results\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entity_validation.js","sourceRoot":"","sources":["../../../../src/modeling/validation/entity_validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { DomainEntityKind } from '../../models/kinds.js'\nimport type { DataDomain } from '../DataDomain.js'\nimport type { DomainEntity } from '../DomainEntity.js'\nimport { ReservedKeywords } from './postgresql.js'\nimport type { DomainValidation } from './rules.js'\n\n/**\n * EntityValidation is a class that performs validation on entities in a data domain.\n *\n * @remarks\n * - We do not need to check for parent uniqueness here, because in the graph there can only be one edge\n * between two nodes. Parent relationships are described by an edge in the graph.\n */\nexport class EntityValidation {\n constructor(protected domain: DataDomain) {}\n\n /**\n * Performs all the validation rules on the entity.\n * If you are interested in a specific rule, use the specific method.\n * @param target The target entity to validate. Can be a string with\n * the entity key or a DomainEntity object.\n */\n validate(target: string | DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n let entity: DomainEntity | undefined\n if (typeof target === 'string') {\n entity = this.domain.findEntity(target)\n } else {\n entity = target\n }\n if (!entity) {\n const message = `The \"${target}\" entity does not exist.`\n const help = `The entity must be defined in the domain.`\n results.push({\n field: '*',\n rule: 'exists',\n message,\n help,\n key: target as string,\n kind: DomainEntityKind,\n severity: 'error',\n })\n return results\n }\n const primaryKey = this.validatePrimaryKey(entity)\n results.push(...primaryKey)\n const minimumProperties = this.minimumRequiredProperties(entity)\n results.push(...minimumProperties)\n const name = this.validateName(entity)\n results.push(...name)\n const unique = this.uniqueName(entity)\n results.push(...unique)\n return results\n }\n\n /**\n * Validates the entity primary key.\n * @param entity The entity to validate\n */\n validatePrimaryKey(entity: DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n const primary = entity.primaryKey()\n if (!primary) {\n const message = `The \"${entity.info.getLabel()}\" entity has no identifier.`\n const help = `An entity that can exists by itself must have identifier defined.`\n results.push({\n field: 'properties',\n rule: 'primary_key',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n return results\n }\n\n /**\n * Checks if an entity has properties through its entire inheritance chain.\n * This includes direct properties and properties inherited from any level of parent entities.\n * @param entity The entity to check\n * @returns True if the entity has properties either directly or through inheritance\n */\n private hasPropertiesInherited(entity: DomainEntity): boolean {\n // Check direct properties first\n if (entity.hasProperties()) {\n return true\n }\n\n // Check all parents recursively\n for (const parent of entity.listParents()) {\n if (this.hasPropertiesInherited(parent)) {\n return true\n }\n }\n\n return false\n }\n\n /**\n * Checks if an entity has associations through its entire inheritance chain.\n * This includes direct associations and associations inherited from any level of parent entities.\n * @param entity The entity to check\n * @returns True if the entity has associations either directly or through inheritance\n */\n private hasAssociationsInherited(entity: DomainEntity): boolean {\n // Check direct associations first\n if (entity.hasAssociations()) {\n return true\n }\n\n // Check all parents recursively\n for (const parent of entity.listParents()) {\n if (this.hasAssociationsInherited(parent)) {\n return true\n }\n }\n\n return false\n }\n\n /**\n * Checks if the entity has the minimum required properties.\n * @param entity The entity to validate\n */\n minimumRequiredProperties(entity: DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n\n // Check if entity has properties or associations through entire inheritance chain\n const hasProperties = this.hasPropertiesInherited(entity)\n const hasAssociations = this.hasAssociationsInherited(entity)\n\n if (!hasProperties && !hasAssociations) {\n const message = `The \"${entity.info.getLabel()}\" entity has no properties. It will be ignored.`\n const help = `Entities that have no properties are ignored in the data domain. No schema will be generated for it.`\n results.push({\n field: 'properties',\n rule: 'required',\n message,\n help,\n severity: 'warning',\n key: entity.key,\n kind: entity.kind,\n })\n }\n return results\n }\n\n /**\n * Validates the entity name.\n *\n * @remarks\n * - An entity must have a name defined.\n * - The name has to follow the same rules as the names in a PostgreSQL database.\n * - Table names must start with a letter (a-z) or underscore (_).\n * - Subsequent characters can be letters, digits (0-9), or underscores (_).\n * - Names are case-insensitive.\n * - The maximum length of a table name is 31 characters\n * - Should be snake case (it's a convention, not an error).\n * - Should not be a reserved word (for example: \"IN\", \"SELECT\", \"FROM\", etc.).\n * @param entity The entity to validate\n */\n validateName(entity: DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n const label = entity.info.getLabel()\n if (!entity.info.name) {\n const message = `The \"${label}\" entity has no name.`\n const help = `An entity must have a name.`\n results.push({\n field: 'name',\n rule: 'required',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n return results\n }\n\n const name = entity.info.name\n if (name.length < 2) {\n const message = `The \"${label}\" entity name is too short.`\n const help = `The name must be at least 2 characters long.`\n results.push({\n field: 'name',\n rule: 'length',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n if (name.length > 31) {\n const message = `The \"${label}\" entity name is too long.`\n const help = `The name must be at most 31 characters long.`\n results.push({\n field: 'name',\n rule: 'length',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n const message = `The \"${label}\" entity name is invalid.`\n const help = `The name must start with a letter (a-z) or underscore (_). Subsequent characters can be letters, digits (0-9), or underscores (_).`\n results.push({\n field: 'name',\n rule: 'format',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n if (/^[A-Z]/.test(name)) {\n const message = `The \"${label}\" entity name is not in snake case.`\n const help = `The name should be in snake case (lowercase letters and underscores).`\n results.push({\n field: 'name',\n rule: 'snake_case',\n message,\n help,\n severity: 'info',\n key: entity.key,\n kind: entity.kind,\n })\n }\n if (ReservedKeywords.has(name.toUpperCase())) {\n const message = `The \"${label}\" entity name is a reserved keyword.`\n const help = `The name should not be a reserved keyword.`\n results.push({\n field: 'name',\n rule: 'reserved',\n message,\n help,\n severity: 'error',\n key: entity.key,\n kind: entity.kind,\n })\n }\n return results\n }\n\n /**\n * Checks if the entity name is unique in the data domain.\n * @param entity The entity to validate\n */\n uniqueName(entity: DomainEntity): DomainValidation[] {\n const results: DomainValidation[] = []\n const name = entity.info.name?.toLowerCase()\n if (!name) {\n return results\n }\n // We need to check the unique names in all entities, including foreign ones.\n for (const other of this.domain.listEntities()) {\n if (other.info.name?.toLowerCase() === name && other.key !== entity.key) {\n const message = `The \"${name}\" entity name is already used in the data domain.`\n const help = `The name must be unique. This includes references to other data domains.`\n results.push({\n field: 'name',\n rule: 'unique',\n message,\n help,\n severity: 'error',\n key: other.key,\n kind: other.kind,\n })\n }\n }\n for (const other of this.domain.listAllForeignEntities()) {\n if (other.info.name?.toLowerCase() === name && other.key !== entity.key) {\n const message = `The \"${name}\" entity name is already used in the foreign data domain.`\n const help = `The name must be unique. This includes references to other data domains.`\n results.push({\n field: 'name',\n rule: 'unique',\n message,\n help,\n severity: 'error',\n key: other.key,\n kind: other.kind,\n parent: other.domain.key,\n })\n }\n }\n return results\n }\n}\n"]}
|