@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,201 @@
|
|
|
1
|
+
import type { JSONSchema7 } from 'json-schema'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for schema filtering during import.
|
|
5
|
+
*/
|
|
6
|
+
export interface SchemaFilteringOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Whether to exclude schemas that are empty objects with no properties.
|
|
9
|
+
* Default: false
|
|
10
|
+
*/
|
|
11
|
+
excludeEmptyObjects?: boolean
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Whether to exclude schemas that only serve as conceptual markers.
|
|
15
|
+
* These are typically schemas with only type: "object" and description
|
|
16
|
+
* but no properties, constraints, or meaningful structure.
|
|
17
|
+
* Default: false
|
|
18
|
+
*/
|
|
19
|
+
excludeConceptualMarkers?: boolean
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Custom predicate function to determine if a schema should be excluded.
|
|
23
|
+
* Return true to exclude the schema from import.
|
|
24
|
+
*/
|
|
25
|
+
customExclusionFilter?: (schema: JSONSchema7, schemaId?: string) => boolean
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* List of schema IDs to explicitly exclude.
|
|
29
|
+
*/
|
|
30
|
+
excludeSchemaIds?: string[]
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Patterns to match against schema titles or IDs for exclusion.
|
|
34
|
+
*/
|
|
35
|
+
excludePatterns?: RegExp[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Strategy class for determining which schemas should be filtered out during import.
|
|
40
|
+
*/
|
|
41
|
+
export class SchemaFilteringStrategy {
|
|
42
|
+
constructor(private options: SchemaFilteringOptions = {}) {}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Determines whether a schema should be excluded from import.
|
|
46
|
+
*/
|
|
47
|
+
shouldExcludeSchema(schema: JSONSchema7, schemaId?: string): boolean {
|
|
48
|
+
// Check explicit exclusion list
|
|
49
|
+
if (schemaId && this.options.excludeSchemaIds?.includes(schemaId)) {
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check pattern exclusions
|
|
54
|
+
if (schemaId && this.options.excludePatterns) {
|
|
55
|
+
for (const pattern of this.options.excludePatterns) {
|
|
56
|
+
if (pattern.test(schemaId) || (schema.title && pattern.test(schema.title))) {
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check for empty objects
|
|
63
|
+
if (this.options.excludeEmptyObjects && this.isEmptyObjectSchema(schema)) {
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check for conceptual markers
|
|
68
|
+
if (this.options.excludeConceptualMarkers && this.isConceptualMarkerSchema(schema)) {
|
|
69
|
+
return true
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Apply custom filter
|
|
73
|
+
if (this.options.customExclusionFilter?.(schema, schemaId)) {
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Checks if a schema represents an empty object with no meaningful structure.
|
|
82
|
+
*/
|
|
83
|
+
private isEmptyObjectSchema(schema: JSONSchema7): boolean {
|
|
84
|
+
return (
|
|
85
|
+
schema.type === 'object' &&
|
|
86
|
+
(!schema.properties || Object.keys(schema.properties).length === 0) &&
|
|
87
|
+
!schema.additionalProperties &&
|
|
88
|
+
!schema.patternProperties &&
|
|
89
|
+
!schema.allOf &&
|
|
90
|
+
!schema.oneOf &&
|
|
91
|
+
!schema.anyOf &&
|
|
92
|
+
!schema.if &&
|
|
93
|
+
!schema.then &&
|
|
94
|
+
!schema.else &&
|
|
95
|
+
!schema.required?.length &&
|
|
96
|
+
!schema.minProperties &&
|
|
97
|
+
!schema.maxProperties
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Checks if a schema is likely a conceptual marker with no structural value.
|
|
103
|
+
*/
|
|
104
|
+
private isConceptualMarkerSchema(schema: JSONSchema7): boolean {
|
|
105
|
+
// Check for schemas that are just empty objects or only have allOf with references
|
|
106
|
+
if (this.isEmptyObjectSchema(schema)) {
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check for schemas that only contain a single allOf with a $ref (like ActionStatusType)
|
|
111
|
+
if (
|
|
112
|
+
schema.type === 'object' &&
|
|
113
|
+
schema.allOf?.length === 1 &&
|
|
114
|
+
typeof schema.allOf[0] === 'object' &&
|
|
115
|
+
'$ref' in schema.allOf[0] &&
|
|
116
|
+
!schema.properties &&
|
|
117
|
+
!schema.additionalProperties
|
|
118
|
+
) {
|
|
119
|
+
return true
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check for schemas that only have description and type but no structure
|
|
123
|
+
if (
|
|
124
|
+
schema.type === 'object' &&
|
|
125
|
+
schema.description &&
|
|
126
|
+
!schema.properties &&
|
|
127
|
+
!schema.additionalProperties &&
|
|
128
|
+
!schema.allOf &&
|
|
129
|
+
!schema.oneOf &&
|
|
130
|
+
!schema.anyOf &&
|
|
131
|
+
Object.keys(schema).filter((key) => !['$schema', '$id', 'title', 'description', 'type'].includes(key)).length ===
|
|
132
|
+
0
|
|
133
|
+
) {
|
|
134
|
+
return true
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Gets a human-readable reason why a schema was excluded.
|
|
142
|
+
*/
|
|
143
|
+
getExclusionReason(schema: JSONSchema7, schemaId?: string): string {
|
|
144
|
+
if (schemaId && this.options.excludeSchemaIds?.includes(schemaId)) {
|
|
145
|
+
return `Schema ID '${schemaId}' is in the exclusion list`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (this.isEmptyObjectSchema(schema)) {
|
|
149
|
+
return 'Schema is an empty object with no structural definition'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (this.isConceptualMarkerSchema(schema)) {
|
|
153
|
+
return 'Schema appears to be a conceptual marker with no concrete structure'
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return 'Schema excluded by custom filter'
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Predefined filtering strategies for common use cases.
|
|
162
|
+
*/
|
|
163
|
+
export const FILTERING_STRATEGIES = {
|
|
164
|
+
/**
|
|
165
|
+
* Strategy for API modeling - excludes schemas that don't contribute to API structure.
|
|
166
|
+
*/
|
|
167
|
+
API_MODELING: new SchemaFilteringStrategy({
|
|
168
|
+
excludeEmptyObjects: true,
|
|
169
|
+
excludeConceptualMarkers: true,
|
|
170
|
+
excludePatterns: [
|
|
171
|
+
/.*Enumeration$/, // Exclude *Enumeration schemas
|
|
172
|
+
/.*Type$/, // Exclude conceptual *Type schemas that are just markers
|
|
173
|
+
],
|
|
174
|
+
}),
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Strategy for database modeling - very strict, only includes schemas with concrete properties.
|
|
178
|
+
*/
|
|
179
|
+
DATABASE_MODELING: new SchemaFilteringStrategy({
|
|
180
|
+
excludeEmptyObjects: true,
|
|
181
|
+
excludeConceptualMarkers: true,
|
|
182
|
+
customExclusionFilter: (schema: JSONSchema7) => {
|
|
183
|
+
// For database modeling, exclude anything that doesn't have properties or clear structure
|
|
184
|
+
return (
|
|
185
|
+
schema.type === 'object' &&
|
|
186
|
+
!schema.properties &&
|
|
187
|
+
!schema.additionalProperties &&
|
|
188
|
+
!schema.allOf?.some(
|
|
189
|
+
(subSchema) => typeof subSchema === 'object' && 'properties' in subSchema && subSchema.properties
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
},
|
|
193
|
+
}),
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Conservative strategy - only excludes obviously empty schemas.
|
|
197
|
+
*/
|
|
198
|
+
CONSERVATIVE: new SchemaFilteringStrategy({
|
|
199
|
+
excludeEmptyObjects: true,
|
|
200
|
+
}),
|
|
201
|
+
} as const
|
|
@@ -17,7 +17,8 @@ export class EntityValidation {
|
|
|
17
17
|
/**
|
|
18
18
|
* Performs all the validation rules on the entity.
|
|
19
19
|
* If you are interested in a specific rule, use the specific method.
|
|
20
|
-
* @param target The target entity to validate. Can be a string with
|
|
20
|
+
* @param target The target entity to validate. Can be a string with
|
|
21
|
+
* the entity key or a DomainEntity object.
|
|
21
22
|
*/
|
|
22
23
|
validate(target: string | DomainEntity): DomainValidation[] {
|
|
23
24
|
const results: DomainValidation[] = []
|
|
@@ -41,21 +42,20 @@ export class EntityValidation {
|
|
|
41
42
|
})
|
|
42
43
|
return results
|
|
43
44
|
}
|
|
44
|
-
const
|
|
45
|
-
results.push(...
|
|
46
|
-
const
|
|
47
|
-
results.push(...
|
|
45
|
+
const primaryKey = this.validatePrimaryKey(entity)
|
|
46
|
+
results.push(...primaryKey)
|
|
47
|
+
const minimumProperties = this.minimumRequiredProperties(entity)
|
|
48
|
+
results.push(...minimumProperties)
|
|
48
49
|
const name = this.validateName(entity)
|
|
49
50
|
results.push(...name)
|
|
50
|
-
const
|
|
51
|
-
results.push(...
|
|
51
|
+
const unique = this.uniqueName(entity)
|
|
52
|
+
results.push(...unique)
|
|
52
53
|
return results
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
|
-
* Validates the entity
|
|
57
|
+
* Validates the entity primary key.
|
|
57
58
|
* @param entity The entity to validate
|
|
58
|
-
* @returns The list of validation messages.
|
|
59
59
|
*/
|
|
60
60
|
validatePrimaryKey(entity: DomainEntity): DomainValidation[] {
|
|
61
61
|
const results: DomainValidation[] = []
|
|
@@ -76,13 +76,62 @@ export class EntityValidation {
|
|
|
76
76
|
return results
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Checks if an entity has properties through its entire inheritance chain.
|
|
81
|
+
* This includes direct properties and properties inherited from any level of parent entities.
|
|
82
|
+
* @param entity The entity to check
|
|
83
|
+
* @returns True if the entity has properties either directly or through inheritance
|
|
84
|
+
*/
|
|
85
|
+
private hasPropertiesInherited(entity: DomainEntity): boolean {
|
|
86
|
+
// Check direct properties first
|
|
87
|
+
if (entity.hasProperties()) {
|
|
88
|
+
return true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check all parents recursively
|
|
92
|
+
for (const parent of entity.listParents()) {
|
|
93
|
+
if (this.hasPropertiesInherited(parent)) {
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Checks if an entity has associations through its entire inheritance chain.
|
|
103
|
+
* This includes direct associations and associations inherited from any level of parent entities.
|
|
104
|
+
* @param entity The entity to check
|
|
105
|
+
* @returns True if the entity has associations either directly or through inheritance
|
|
106
|
+
*/
|
|
107
|
+
private hasAssociationsInherited(entity: DomainEntity): boolean {
|
|
108
|
+
// Check direct associations first
|
|
109
|
+
if (entity.hasAssociations()) {
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check all parents recursively
|
|
114
|
+
for (const parent of entity.listParents()) {
|
|
115
|
+
if (this.hasAssociationsInherited(parent)) {
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
79
123
|
/**
|
|
80
124
|
* Checks if the entity has the minimum required properties.
|
|
81
125
|
* @param entity The entity to validate
|
|
82
126
|
*/
|
|
83
127
|
minimumRequiredProperties(entity: DomainEntity): DomainValidation[] {
|
|
84
128
|
const results: DomainValidation[] = []
|
|
85
|
-
|
|
129
|
+
|
|
130
|
+
// Check if entity has properties or associations through entire inheritance chain
|
|
131
|
+
const hasProperties = this.hasPropertiesInherited(entity)
|
|
132
|
+
const hasAssociations = this.hasAssociationsInherited(entity)
|
|
133
|
+
|
|
134
|
+
if (!hasProperties && !hasAssociations) {
|
|
86
135
|
const message = `The "${entity.info.getLabel()}" entity has no properties. It will be ignored.`
|
|
87
136
|
const help = `Entities that have no properties are ignored in the data domain. No schema will be generated for it.`
|
|
88
137
|
results.push({
|