@kravc/schema 2.7.6 → 2.8.0-alpha.1
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/README.md +19 -14
- package/dist/CredentialFactory.d.ts +345 -0
- package/dist/CredentialFactory.d.ts.map +1 -0
- package/dist/CredentialFactory.js +381 -0
- package/dist/CredentialFactory.js.map +1 -0
- package/dist/Schema.d.ts +448 -0
- package/dist/Schema.d.ts.map +1 -0
- package/dist/Schema.js +506 -0
- package/dist/Schema.js.map +1 -0
- package/dist/ValidationError.d.ts +70 -0
- package/dist/ValidationError.d.ts.map +1 -0
- package/dist/ValidationError.js +78 -0
- package/dist/ValidationError.js.map +1 -0
- package/dist/Validator.d.ts +483 -0
- package/dist/Validator.d.ts.map +1 -0
- package/dist/Validator.js +570 -0
- package/dist/Validator.js.map +1 -0
- package/dist/helpers/JsonSchema.d.ts +99 -0
- package/dist/helpers/JsonSchema.d.ts.map +1 -0
- package/dist/helpers/JsonSchema.js +3 -0
- package/dist/helpers/JsonSchema.js.map +1 -0
- package/dist/helpers/cleanupAttributes.d.ts +34 -0
- package/dist/helpers/cleanupAttributes.d.ts.map +1 -0
- package/dist/helpers/cleanupAttributes.js +113 -0
- package/dist/helpers/cleanupAttributes.js.map +1 -0
- package/dist/helpers/cleanupNulls.d.ts +27 -0
- package/dist/helpers/cleanupNulls.d.ts.map +1 -0
- package/dist/helpers/cleanupNulls.js +96 -0
- package/dist/helpers/cleanupNulls.js.map +1 -0
- package/dist/helpers/createSchemasMap.d.ts +67 -0
- package/dist/helpers/createSchemasMap.d.ts.map +1 -0
- package/dist/helpers/createSchemasMap.js +200 -0
- package/dist/helpers/createSchemasMap.js.map +1 -0
- package/dist/helpers/getReferenceIds.d.ts +169 -0
- package/dist/helpers/getReferenceIds.d.ts.map +1 -0
- package/dist/helpers/getReferenceIds.js +241 -0
- package/dist/helpers/getReferenceIds.js.map +1 -0
- package/dist/helpers/got.d.ts +60 -0
- package/dist/helpers/got.d.ts.map +1 -0
- package/dist/helpers/got.js +72 -0
- package/dist/helpers/got.js.map +1 -0
- package/dist/helpers/mapObjectProperties.d.ts +150 -0
- package/dist/helpers/mapObjectProperties.d.ts.map +1 -0
- package/dist/helpers/mapObjectProperties.js +229 -0
- package/dist/helpers/mapObjectProperties.js.map +1 -0
- package/dist/helpers/normalizeAttributes.d.ts +213 -0
- package/dist/helpers/normalizeAttributes.d.ts.map +1 -0
- package/dist/helpers/normalizeAttributes.js +243 -0
- package/dist/helpers/normalizeAttributes.js.map +1 -0
- package/dist/helpers/normalizeProperties.d.ts +168 -0
- package/dist/helpers/normalizeProperties.d.ts.map +1 -0
- package/dist/helpers/normalizeProperties.js +223 -0
- package/dist/helpers/normalizeProperties.js.map +1 -0
- package/dist/helpers/normalizeRequired.d.ts +159 -0
- package/dist/helpers/normalizeRequired.d.ts.map +1 -0
- package/dist/helpers/normalizeRequired.js +206 -0
- package/dist/helpers/normalizeRequired.js.map +1 -0
- package/dist/helpers/normalizeType.d.ts +81 -0
- package/dist/helpers/normalizeType.d.ts.map +1 -0
- package/dist/helpers/normalizeType.js +210 -0
- package/dist/helpers/normalizeType.js.map +1 -0
- package/dist/helpers/nullifyEmptyValues.d.ts +139 -0
- package/dist/helpers/nullifyEmptyValues.d.ts.map +1 -0
- package/dist/helpers/nullifyEmptyValues.js +191 -0
- package/dist/helpers/nullifyEmptyValues.js.map +1 -0
- package/dist/helpers/removeRequiredAndDefault.d.ts +106 -0
- package/dist/helpers/removeRequiredAndDefault.d.ts.map +1 -0
- package/dist/helpers/removeRequiredAndDefault.js +138 -0
- package/dist/helpers/removeRequiredAndDefault.js.map +1 -0
- package/dist/helpers/validateId.d.ts +39 -0
- package/dist/helpers/validateId.d.ts.map +1 -0
- package/dist/helpers/validateId.js +51 -0
- package/dist/helpers/validateId.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/ld/documentLoader.d.ts +8 -0
- package/dist/ld/documentLoader.d.ts.map +1 -0
- package/dist/ld/documentLoader.js +24 -0
- package/dist/ld/documentLoader.js.map +1 -0
- package/dist/ld/getLinkedDataAttributeType.d.ts +10 -0
- package/dist/ld/getLinkedDataAttributeType.d.ts.map +1 -0
- package/dist/ld/getLinkedDataAttributeType.js +32 -0
- package/dist/ld/getLinkedDataAttributeType.js.map +1 -0
- package/dist/ld/getLinkedDataContext.d.ts +19 -0
- package/dist/ld/getLinkedDataContext.d.ts.map +1 -0
- package/dist/ld/getLinkedDataContext.js +50 -0
- package/dist/ld/getLinkedDataContext.js.map +1 -0
- package/eslint.config.mjs +32 -52
- package/examples/credentials/createAccountCredential.ts +27 -0
- package/examples/credentials/createMineSweeperScoreCredential.ts +115 -0
- package/examples/index.ts +7 -0
- package/examples/schemas/FavoriteItemSchema.ts +27 -0
- package/examples/{Preferences.yaml → schemas/Preferences.yaml} +2 -0
- package/examples/schemas/PreferencesSchema.ts +29 -0
- package/examples/schemas/ProfileSchema.ts +91 -0
- package/examples/schemas/Status.yaml +3 -0
- package/examples/schemas/StatusSchema.ts +12 -0
- package/jest.config.mjs +5 -0
- package/package.json +27 -20
- package/src/CredentialFactory.ts +392 -0
- package/src/Schema.ts +583 -0
- package/src/ValidationError.ts +90 -0
- package/src/Validator.ts +603 -0
- package/src/__tests__/CredentialFactory.test.ts +588 -0
- package/src/__tests__/Schema.test.ts +371 -0
- package/src/__tests__/ValidationError.test.ts +235 -0
- package/src/__tests__/Validator.test.ts +787 -0
- package/src/helpers/JsonSchema.ts +119 -0
- package/src/helpers/__tests__/cleanupAttributes.test.ts +943 -0
- package/src/helpers/__tests__/cleanupNulls.test.ts +772 -0
- package/src/helpers/__tests__/createSchemasMap.test.ts +238 -0
- package/src/helpers/__tests__/getReferenceIds.test.ts +975 -0
- package/src/helpers/__tests__/got.test.ts +193 -0
- package/src/helpers/__tests__/mapObjectProperties.test.ts +1126 -0
- package/src/helpers/__tests__/normalizeAttributes.test.ts +1435 -0
- package/src/helpers/__tests__/normalizeProperties.test.ts +727 -0
- package/src/helpers/__tests__/normalizeRequired.test.ts +669 -0
- package/src/helpers/__tests__/normalizeType.test.ts +772 -0
- package/src/helpers/__tests__/nullifyEmptyValues.test.ts +735 -0
- package/src/helpers/__tests__/removeRequiredAndDefault.test.ts +734 -0
- package/src/helpers/__tests__/validateId.test.ts +118 -0
- package/src/helpers/cleanupAttributes.ts +151 -0
- package/src/helpers/cleanupNulls.ts +106 -0
- package/src/helpers/createSchemasMap.ts +212 -0
- package/src/helpers/getReferenceIds.ts +273 -0
- package/src/helpers/got.ts +73 -0
- package/src/helpers/mapObjectProperties.ts +272 -0
- package/src/helpers/normalizeAttributes.ts +247 -0
- package/src/helpers/normalizeProperties.ts +249 -0
- package/src/helpers/normalizeRequired.ts +233 -0
- package/src/helpers/normalizeType.ts +235 -0
- package/src/helpers/nullifyEmptyValues.ts +207 -0
- package/src/helpers/removeRequiredAndDefault.ts +151 -0
- package/src/helpers/validateId.ts +53 -0
- package/src/index.ts +17 -0
- package/src/ld/__tests__/documentLoader.test.ts +57 -0
- package/src/ld/__tests__/getLinkedDataAttributeType.test.ts +212 -0
- package/src/ld/__tests__/getLinkedDataContext.test.ts +378 -0
- package/src/ld/documentLoader.ts +28 -0
- package/src/ld/getLinkedDataAttributeType.ts +46 -0
- package/src/ld/getLinkedDataContext.ts +80 -0
- package/tsconfig.json +27 -0
- package/types/credentials-context.d.ts +14 -0
- package/types/security-context.d.ts +6 -0
- package/examples/Status.yaml +0 -3
- package/examples/createAccountCredential.js +0 -27
- package/examples/createMineSweeperScoreCredential.js +0 -63
- package/examples/index.js +0 -9
- package/src/CredentialFactory.js +0 -67
- package/src/CredentialFactory.spec.js +0 -131
- package/src/Schema.js +0 -104
- package/src/Schema.spec.js +0 -172
- package/src/ValidationError.js +0 -31
- package/src/Validator.js +0 -128
- package/src/Validator.spec.js +0 -355
- package/src/helpers/cleanupAttributes.js +0 -71
- package/src/helpers/cleanupNulls.js +0 -42
- package/src/helpers/getReferenceIds.js +0 -71
- package/src/helpers/mapObject.js +0 -65
- package/src/helpers/normalizeAttributes.js +0 -28
- package/src/helpers/normalizeProperties.js +0 -61
- package/src/helpers/normalizeRequired.js +0 -37
- package/src/helpers/normalizeType.js +0 -41
- package/src/helpers/nullifyEmptyValues.js +0 -57
- package/src/helpers/removeRequiredAndDefault.js +0 -30
- package/src/helpers/validateId.js +0 -19
- package/src/index.d.ts +0 -25
- package/src/index.js +0 -8
- package/src/ld/documentLoader.js +0 -25
- package/src/ld/documentLoader.spec.js +0 -12
- package/src/ld/getLinkedDataContext.js +0 -63
- package/src/ld/getLinkedDataType.js +0 -38
- /package/examples/{FavoriteItem.yaml → schemas/FavoriteItem.yaml} +0 -0
- /package/examples/{Profile.yaml → schemas/Profile.yaml} +0 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
import normalizeProperties from '../normalizeProperties';
|
|
2
|
+
import type { EnumSchema, PropertiesSchema } from '../JsonSchema';
|
|
3
|
+
|
|
4
|
+
describe('normalizeProperties(schema)', () => {
|
|
5
|
+
describe('enum schema', () => {
|
|
6
|
+
it('should set type to "string" when enum schema has no type', () => {
|
|
7
|
+
const schema: EnumSchema = {
|
|
8
|
+
enum: ['value1', 'value2', 'value3']
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
normalizeProperties(schema);
|
|
12
|
+
|
|
13
|
+
expect(schema.type).toBe('string');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should keep existing type when enum schema already has type', () => {
|
|
17
|
+
const schema: EnumSchema = {
|
|
18
|
+
enum: ['value1', 'value2'],
|
|
19
|
+
type: 'number'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
normalizeProperties(schema);
|
|
23
|
+
|
|
24
|
+
expect(schema.type).toBe('number');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should set type to "string" for enum with string values even when type is missing', () => {
|
|
28
|
+
const schema: EnumSchema = {
|
|
29
|
+
enum: ['red', 'green', 'blue']
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
normalizeProperties(schema);
|
|
33
|
+
|
|
34
|
+
expect(schema.type).toBe('string');
|
|
35
|
+
expect(schema.enum).toEqual(['red', 'green', 'blue']);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should preserve type "number" for enum with number values', () => {
|
|
39
|
+
const schema: EnumSchema = {
|
|
40
|
+
enum: [1, 2, 3],
|
|
41
|
+
type: 'number'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
normalizeProperties(schema);
|
|
45
|
+
|
|
46
|
+
expect(schema.type).toBe('number');
|
|
47
|
+
expect(schema.enum).toEqual([1, 2, 3]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should not modify other enum schema properties', () => {
|
|
51
|
+
const schema: EnumSchema = {
|
|
52
|
+
enum: ['a', 'b', 'c'],
|
|
53
|
+
default: 'a',
|
|
54
|
+
description: 'Test enum'
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
normalizeProperties(schema);
|
|
58
|
+
|
|
59
|
+
expect(schema.type).toBe('string');
|
|
60
|
+
expect(schema.default).toBe('a');
|
|
61
|
+
expect(schema.description).toBe('Test enum');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('properties schema', () => {
|
|
66
|
+
describe('reference properties ($ref)', () => {
|
|
67
|
+
it('should skip $ref properties and not modify them', () => {
|
|
68
|
+
const schema: PropertiesSchema = {
|
|
69
|
+
refField: { $ref: '#/definitions/SomeSchema' },
|
|
70
|
+
normalField: { type: 'string' }
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
normalizeProperties(schema);
|
|
74
|
+
|
|
75
|
+
expect(schema.refField).toEqual({ $ref: '#/definitions/SomeSchema' });
|
|
76
|
+
expect(schema.normalField.type).toBe('string');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('type inference', () => {
|
|
81
|
+
it('should set type to "object" when property has properties but no type', () => {
|
|
82
|
+
const schema: PropertiesSchema = {
|
|
83
|
+
objectField: {
|
|
84
|
+
properties: {
|
|
85
|
+
nested: { type: 'string' }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
normalizeProperties(schema);
|
|
91
|
+
|
|
92
|
+
expect(schema.objectField.type).toBe('object');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should set type to "array" when property has items but no type', () => {
|
|
96
|
+
const schema: PropertiesSchema = {
|
|
97
|
+
arrayField: {
|
|
98
|
+
items: { type: 'string' }
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
normalizeProperties(schema);
|
|
103
|
+
|
|
104
|
+
expect(schema.arrayField.type).toBe('array');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should set type to "string" as default when property has no type, items, or properties', () => {
|
|
108
|
+
const schema: PropertiesSchema = {
|
|
109
|
+
stringField: {}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
normalizeProperties(schema);
|
|
113
|
+
|
|
114
|
+
expect(schema.stringField.type).toBe('string');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should prioritize properties over items when both exist and type is missing', () => {
|
|
118
|
+
const schema: PropertiesSchema = {
|
|
119
|
+
conflictingField: {
|
|
120
|
+
properties: {
|
|
121
|
+
nested: { type: 'string' }
|
|
122
|
+
},
|
|
123
|
+
items: { type: 'number' }
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
normalizeProperties(schema);
|
|
128
|
+
|
|
129
|
+
expect(schema.conflictingField.type).toBe('object');
|
|
130
|
+
expect(schema.conflictingField.properties).toBeDefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should not override existing type', () => {
|
|
134
|
+
const schema: PropertiesSchema = {
|
|
135
|
+
numberField: { type: 'number' },
|
|
136
|
+
booleanField: { type: 'boolean' },
|
|
137
|
+
integerField: { type: 'integer' }
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
normalizeProperties(schema);
|
|
141
|
+
|
|
142
|
+
expect(schema.numberField.type).toBe('number');
|
|
143
|
+
expect(schema.booleanField.type).toBe('boolean');
|
|
144
|
+
expect(schema.integerField.type).toBe('integer');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should preserve existing type even when conflicting structure exists (type: object with items)', () => {
|
|
148
|
+
const schema: PropertiesSchema = {
|
|
149
|
+
objectWithItems: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
nested: { type: 'string' }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
// Manually set items to test conflicting structure (object shouldn't have items)
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
(schema.objectWithItems as any).items = { type: 'number' };
|
|
159
|
+
|
|
160
|
+
normalizeProperties(schema);
|
|
161
|
+
|
|
162
|
+
expect(schema.objectWithItems.type).toBe('object');
|
|
163
|
+
expect(schema.objectWithItems.properties).toBeDefined();
|
|
164
|
+
// Object type should be normalized, items should be ignored
|
|
165
|
+
expect(schema.objectWithItems.properties!.nested.type).toBe('string');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should preserve existing type even when conflicting structure exists (type: array with properties)', () => {
|
|
169
|
+
const schema: PropertiesSchema = {
|
|
170
|
+
arrayWithProperties: {
|
|
171
|
+
type: 'array',
|
|
172
|
+
items: { type: 'string' }
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
// Manually set properties to test conflicting structure (array shouldn't have properties)
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
177
|
+
(schema.arrayWithProperties as any).properties = {
|
|
178
|
+
nested: { type: 'number' }
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
normalizeProperties(schema);
|
|
182
|
+
|
|
183
|
+
expect(schema.arrayWithProperties.type).toBe('array');
|
|
184
|
+
expect(schema.arrayWithProperties.items).toBeDefined();
|
|
185
|
+
expect(schema.arrayWithProperties.items!.type).toBe('string');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should preserve existing type even when conflicting structure exists (type: string with properties)', () => {
|
|
189
|
+
const schema: PropertiesSchema = {
|
|
190
|
+
stringWithProperties: {
|
|
191
|
+
type: 'string'
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
// Manually set properties to test conflicting structure (string shouldn't have properties)
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
|
+
(schema.stringWithProperties as any).properties = {
|
|
197
|
+
nested: { type: 'number' }
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
normalizeProperties(schema);
|
|
201
|
+
|
|
202
|
+
expect(schema.stringWithProperties.type).toBe('string');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should preserve existing type even when conflicting structure exists (type: string with items)', () => {
|
|
206
|
+
const schema: PropertiesSchema = {
|
|
207
|
+
stringWithItems: {
|
|
208
|
+
type: 'string'
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
// Manually set items to test conflicting structure (string shouldn't have items)
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
|
+
(schema.stringWithItems as any).items = { type: 'number' };
|
|
214
|
+
|
|
215
|
+
normalizeProperties(schema);
|
|
216
|
+
|
|
217
|
+
expect(schema.stringWithItems.type).toBe('string');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('object properties', () => {
|
|
222
|
+
it('should create empty properties object for object type without properties', () => {
|
|
223
|
+
const schema: PropertiesSchema = {
|
|
224
|
+
objectField: {
|
|
225
|
+
type: 'object'
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
normalizeProperties(schema);
|
|
230
|
+
|
|
231
|
+
expect(schema.objectField.type).toBe('object');
|
|
232
|
+
expect(schema.objectField.properties).toEqual({});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should recursively normalize nested object properties', () => {
|
|
236
|
+
const schema: PropertiesSchema = {
|
|
237
|
+
nestedObject: {
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {
|
|
240
|
+
nestedField: {},
|
|
241
|
+
nestedObject2: {
|
|
242
|
+
properties: {
|
|
243
|
+
deepField: {}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
normalizeProperties(schema);
|
|
251
|
+
|
|
252
|
+
expect(schema.nestedObject.type).toBe('object');
|
|
253
|
+
expect(schema.nestedObject.properties!.nestedField.type).toBe('string');
|
|
254
|
+
expect(schema.nestedObject.properties!.nestedObject2.type).toBe('object');
|
|
255
|
+
expect(schema.nestedObject.properties!.nestedObject2.properties!.deepField.type).toBe('string');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should preserve existing properties when normalizing object', () => {
|
|
259
|
+
const schema: PropertiesSchema = {
|
|
260
|
+
objectField: {
|
|
261
|
+
type: 'object',
|
|
262
|
+
properties: {
|
|
263
|
+
existing: { type: 'number' }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
normalizeProperties(schema);
|
|
269
|
+
|
|
270
|
+
expect(schema.objectField.properties!.existing.type).toBe('number');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should handle object with undefined properties', () => {
|
|
274
|
+
const schema = {
|
|
275
|
+
objectField: {
|
|
276
|
+
type: 'object' as const,
|
|
277
|
+
properties: undefined
|
|
278
|
+
}
|
|
279
|
+
} as PropertiesSchema;
|
|
280
|
+
|
|
281
|
+
normalizeProperties(schema);
|
|
282
|
+
|
|
283
|
+
expect(schema.objectField.type).toBe('object');
|
|
284
|
+
expect(schema.objectField.properties).toEqual({});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should handle object with null properties', () => {
|
|
288
|
+
const schema = {
|
|
289
|
+
objectField: {
|
|
290
|
+
type: 'object' as const,
|
|
291
|
+
properties: null
|
|
292
|
+
}
|
|
293
|
+
} as PropertiesSchema;
|
|
294
|
+
|
|
295
|
+
normalizeProperties(schema);
|
|
296
|
+
|
|
297
|
+
expect(schema.objectField.type).toBe('object');
|
|
298
|
+
// When properties is null, it gets normalized to {} and recursive call uses || {}
|
|
299
|
+
expect(schema.objectField.properties).toEqual({});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should handle object type with undefined properties (tests || {} fallback)', () => {
|
|
303
|
+
const schema = {
|
|
304
|
+
objectField: {
|
|
305
|
+
type: 'object' as const,
|
|
306
|
+
properties: undefined
|
|
307
|
+
}
|
|
308
|
+
} as PropertiesSchema;
|
|
309
|
+
|
|
310
|
+
normalizeProperties(schema);
|
|
311
|
+
|
|
312
|
+
expect(schema.objectField.type).toBe('object');
|
|
313
|
+
// When properties is undefined, it gets set to {} on line 56, then || {} on line 59 uses the set value
|
|
314
|
+
expect(schema.objectField.properties).toEqual({});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should handle object with properties explicitly set to null after type is set (tests || {} fallback on line 59)', () => {
|
|
318
|
+
// Create an object where properties is null but type is already 'object'
|
|
319
|
+
// This tests the || {} fallback on line 59 when properties is falsy
|
|
320
|
+
const schema: PropertiesSchema = {
|
|
321
|
+
objectField: {
|
|
322
|
+
type: 'object',
|
|
323
|
+
// Manually set properties to null to test the || {} branch
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
// Manually set properties to null after schema creation to bypass type checking
|
|
327
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
328
|
+
(schema.objectField as any).properties = null;
|
|
329
|
+
|
|
330
|
+
normalizeProperties(schema);
|
|
331
|
+
|
|
332
|
+
expect(schema.objectField.type).toBe('object');
|
|
333
|
+
// The || {} fallback should handle null properties
|
|
334
|
+
expect(schema.objectField.properties).toEqual({});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('array properties', () => {
|
|
340
|
+
it('should set items type to "string" when array has no items', () => {
|
|
341
|
+
const schema: PropertiesSchema = {
|
|
342
|
+
arrayField: {
|
|
343
|
+
type: 'array'
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
normalizeProperties(schema);
|
|
348
|
+
|
|
349
|
+
expect(schema.arrayField.type).toBe('array');
|
|
350
|
+
expect(schema.arrayField.items).toEqual({ type: 'string' });
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should set items type to "object" and normalize when items have properties', () => {
|
|
354
|
+
const schema: PropertiesSchema = {
|
|
355
|
+
arrayField: {
|
|
356
|
+
type: 'array',
|
|
357
|
+
items: {
|
|
358
|
+
properties: {
|
|
359
|
+
itemField: {}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
normalizeProperties(schema);
|
|
366
|
+
|
|
367
|
+
expect(schema.arrayField.type).toBe('array');
|
|
368
|
+
expect(schema.arrayField.items!.type).toBe('object');
|
|
369
|
+
expect((schema.arrayField.items as unknown as { properties?: { itemField: { type?: string } } }).properties!.itemField.type).toBe('string');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should recursively normalize nested properties in array items', () => {
|
|
373
|
+
const schema: PropertiesSchema = {
|
|
374
|
+
arrayField: {
|
|
375
|
+
type: 'array',
|
|
376
|
+
items: {
|
|
377
|
+
type: 'object',
|
|
378
|
+
properties: {
|
|
379
|
+
nestedArray: {
|
|
380
|
+
items: {
|
|
381
|
+
properties: {
|
|
382
|
+
deepField: {}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
normalizeProperties(schema);
|
|
392
|
+
|
|
393
|
+
expect(schema.arrayField.type).toBe('array');
|
|
394
|
+
const items = schema.arrayField.items as unknown as { type?: string; properties?: { nestedArray: { type?: string; items?: { type?: string; properties?: { deepField: { type?: string } } } } } };
|
|
395
|
+
expect(items.type).toBe('object');
|
|
396
|
+
expect(items.properties!.nestedArray.type).toBe('array');
|
|
397
|
+
expect(items.properties!.nestedArray.items!.type).toBe('object');
|
|
398
|
+
expect(items.properties!.nestedArray.items!.properties!.deepField.type).toBe('string');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should preserve existing items type when array has items with type', () => {
|
|
402
|
+
const schema: PropertiesSchema = {
|
|
403
|
+
arrayField: {
|
|
404
|
+
type: 'array',
|
|
405
|
+
items: { type: 'number' }
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
normalizeProperties(schema);
|
|
410
|
+
|
|
411
|
+
expect(schema.arrayField.items!.type).toBe('number');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('should handle array with undefined items', () => {
|
|
415
|
+
const schema = {
|
|
416
|
+
arrayField: {
|
|
417
|
+
type: 'array' as const,
|
|
418
|
+
items: undefined
|
|
419
|
+
}
|
|
420
|
+
} as PropertiesSchema;
|
|
421
|
+
|
|
422
|
+
normalizeProperties(schema);
|
|
423
|
+
|
|
424
|
+
expect(schema.arrayField.type).toBe('array');
|
|
425
|
+
expect(schema.arrayField.items).toEqual({ type: 'string' });
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should handle array items without properties (should not set type to object)', () => {
|
|
429
|
+
const schema: PropertiesSchema = {
|
|
430
|
+
arrayField: {
|
|
431
|
+
type: 'array',
|
|
432
|
+
items: {
|
|
433
|
+
type: 'string'
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
normalizeProperties(schema);
|
|
439
|
+
|
|
440
|
+
expect(schema.arrayField.type).toBe('array');
|
|
441
|
+
expect(schema.arrayField.items!.type).toBe('string');
|
|
442
|
+
// Items without properties should not have type set to 'object'
|
|
443
|
+
expect((schema.arrayField.items as unknown as { properties?: unknown }).properties).toBeUndefined();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it('should handle array items with undefined properties', () => {
|
|
447
|
+
const schema = {
|
|
448
|
+
arrayField: {
|
|
449
|
+
type: 'array' as const,
|
|
450
|
+
items: {
|
|
451
|
+
properties: undefined
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
} as PropertiesSchema;
|
|
455
|
+
|
|
456
|
+
normalizeProperties(schema);
|
|
457
|
+
|
|
458
|
+
expect(schema.arrayField.type).toBe('array');
|
|
459
|
+
// Items with undefined properties should not have type set to 'object'
|
|
460
|
+
expect((schema.arrayField.items as unknown as { type?: string }).type).toBeUndefined();
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('should handle array items with null properties', () => {
|
|
464
|
+
const schema = {
|
|
465
|
+
arrayField: {
|
|
466
|
+
type: 'array' as const,
|
|
467
|
+
items: {
|
|
468
|
+
properties: null
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} as PropertiesSchema;
|
|
472
|
+
|
|
473
|
+
normalizeProperties(schema);
|
|
474
|
+
|
|
475
|
+
expect(schema.arrayField.type).toBe('array');
|
|
476
|
+
// Items with null properties are treated as existing (not undefined), so type is set to 'object'
|
|
477
|
+
expect((schema.arrayField.items as unknown as { type?: string }).type).toBe('object');
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should handle array items that is an empty object (tests destructuring with items existing)', () => {
|
|
481
|
+
// This ensures hasItems is true and we enter the block, testing the destructuring
|
|
482
|
+
const schema: PropertiesSchema = {
|
|
483
|
+
arrayField: {
|
|
484
|
+
type: 'array',
|
|
485
|
+
items: {} // Empty object, hasItems will be true
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
normalizeProperties(schema);
|
|
490
|
+
|
|
491
|
+
expect(schema.arrayField.type).toBe('array');
|
|
492
|
+
// Items exists but has no properties, so type should not be set to 'object'
|
|
493
|
+
expect((schema.arrayField.items as unknown as { type?: string }).type).toBeUndefined();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should skip array items that are references ($ref)', () => {
|
|
497
|
+
const schema: PropertiesSchema = {
|
|
498
|
+
arrayField: {
|
|
499
|
+
type: 'array',
|
|
500
|
+
items: { $ref: '#/definitions/SomeSchema' }
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
normalizeProperties(schema);
|
|
505
|
+
|
|
506
|
+
expect(schema.arrayField.type).toBe('array');
|
|
507
|
+
expect(schema.arrayField.items).toEqual({ $ref: '#/definitions/SomeSchema' });
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('should handle array items that are enum schemas (enum normalization only applies to top-level)', () => {
|
|
511
|
+
const schema: PropertiesSchema = {
|
|
512
|
+
arrayField: {
|
|
513
|
+
type: 'array',
|
|
514
|
+
items: {
|
|
515
|
+
enum: ['value1', 'value2', 'value3']
|
|
516
|
+
} as EnumSchema
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
normalizeProperties(schema);
|
|
521
|
+
|
|
522
|
+
expect(schema.arrayField.type).toBe('array');
|
|
523
|
+
const items = schema.arrayField.items as unknown as EnumSchema;
|
|
524
|
+
// Enum normalization only applies to top-level EnumSchema, not nested items
|
|
525
|
+
// So items enum schema won't get type set automatically
|
|
526
|
+
expect(items.type).toBeUndefined();
|
|
527
|
+
expect(items.enum).toEqual(['value1', 'value2', 'value3']);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('should set type to object when items has properties even if items already has a type', () => {
|
|
531
|
+
const schema: PropertiesSchema = {
|
|
532
|
+
arrayField: {
|
|
533
|
+
type: 'array',
|
|
534
|
+
items: {
|
|
535
|
+
type: 'number'
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
// Manually set properties to test items with both type and properties
|
|
540
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
541
|
+
(schema.arrayField.items as any).properties = {
|
|
542
|
+
nested: { type: 'string' }
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
normalizeProperties(schema);
|
|
546
|
+
|
|
547
|
+
expect(schema.arrayField.type).toBe('array');
|
|
548
|
+
// When items has properties, type is set to 'object' (line 236), overriding existing type
|
|
549
|
+
const items = schema.arrayField.items as unknown as { type?: string; properties?: { nested: { type?: string } } };
|
|
550
|
+
expect(items.type).toBe('object');
|
|
551
|
+
// Properties should still be normalized
|
|
552
|
+
if (items.properties) {
|
|
553
|
+
expect(items.properties.nested.type).toBe('string');
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
describe('complex nested structures', () => {
|
|
559
|
+
it('should normalize complex nested structure with objects and arrays', () => {
|
|
560
|
+
const schema: PropertiesSchema = {
|
|
561
|
+
user: {
|
|
562
|
+
properties: {
|
|
563
|
+
name: {},
|
|
564
|
+
addresses: {
|
|
565
|
+
items: {
|
|
566
|
+
properties: {
|
|
567
|
+
street: {},
|
|
568
|
+
city: {}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
metadata: {
|
|
573
|
+
properties: {
|
|
574
|
+
tags: {
|
|
575
|
+
items: {}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
normalizeProperties(schema);
|
|
584
|
+
|
|
585
|
+
expect(schema.user.type).toBe('object');
|
|
586
|
+
expect(schema.user.properties!.name.type).toBe('string');
|
|
587
|
+
expect(schema.user.properties!.addresses.type).toBe('array');
|
|
588
|
+
expect((schema.user.properties!.addresses.items as unknown as { type?: string; properties?: { street: { type?: string }; city: { type?: string } } }).type).toBe('object');
|
|
589
|
+
expect((schema.user.properties!.addresses.items as unknown as { type?: string; properties?: { street: { type?: string }; city: { type?: string } } }).properties!.street.type).toBe('string');
|
|
590
|
+
expect((schema.user.properties!.addresses.items as unknown as { type?: string; properties?: { street: { type?: string }; city: { type?: string } } }).properties!.city.type).toBe('string');
|
|
591
|
+
expect(schema.user.properties!.metadata.type).toBe('object');
|
|
592
|
+
// Array items without properties don't get a type set
|
|
593
|
+
expect((schema.user.properties!.metadata.properties!.tags.items as unknown as { type?: string }).type).toBeUndefined();
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('should handle mixed properties with refs, objects, arrays, and primitives', () => {
|
|
597
|
+
const schema: PropertiesSchema = {
|
|
598
|
+
refField: { $ref: '#/definitions/Ref' },
|
|
599
|
+
stringField: {},
|
|
600
|
+
numberField: { type: 'number' },
|
|
601
|
+
objectField: {
|
|
602
|
+
properties: {
|
|
603
|
+
nested: {}
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
arrayField: {
|
|
607
|
+
items: {}
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
normalizeProperties(schema);
|
|
612
|
+
|
|
613
|
+
expect(schema.refField).toEqual({ $ref: '#/definitions/Ref' });
|
|
614
|
+
expect(schema.stringField.type).toBe('string');
|
|
615
|
+
expect(schema.numberField.type).toBe('number');
|
|
616
|
+
expect(schema.objectField.type).toBe('object');
|
|
617
|
+
expect(schema.objectField.properties!.nested.type).toBe('string');
|
|
618
|
+
expect(schema.arrayField.type).toBe('array');
|
|
619
|
+
// Array items without properties don't get a type set
|
|
620
|
+
expect(schema.arrayField.items!.type).toBeUndefined();
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
describe('edge cases', () => {
|
|
625
|
+
it('should handle empty properties schema', () => {
|
|
626
|
+
const schema: PropertiesSchema = {};
|
|
627
|
+
|
|
628
|
+
normalizeProperties(schema);
|
|
629
|
+
|
|
630
|
+
expect(schema).toEqual({});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('should handle property with type but no properties or items', () => {
|
|
634
|
+
const schema: PropertiesSchema = {
|
|
635
|
+
stringField: { type: 'string' },
|
|
636
|
+
numberField: { type: 'number' }
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
normalizeProperties(schema);
|
|
640
|
+
|
|
641
|
+
expect(schema.stringField.type).toBe('string');
|
|
642
|
+
expect(schema.numberField.type).toBe('number');
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('should handle object with empty properties object', () => {
|
|
646
|
+
const schema: PropertiesSchema = {
|
|
647
|
+
emptyObject: {
|
|
648
|
+
type: 'object',
|
|
649
|
+
properties: {}
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
normalizeProperties(schema);
|
|
654
|
+
|
|
655
|
+
expect(schema.emptyObject.type).toBe('object');
|
|
656
|
+
expect(schema.emptyObject.properties).toEqual({});
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('should handle object properties containing $ref properties', () => {
|
|
660
|
+
const schema: PropertiesSchema = {
|
|
661
|
+
objectField: {
|
|
662
|
+
type: 'object',
|
|
663
|
+
properties: {
|
|
664
|
+
refField: { $ref: '#/definitions/SomeSchema' },
|
|
665
|
+
normalField: {}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
normalizeProperties(schema);
|
|
671
|
+
|
|
672
|
+
expect(schema.objectField.type).toBe('object');
|
|
673
|
+
expect(schema.objectField.properties!.refField).toEqual({ $ref: '#/definitions/SomeSchema' });
|
|
674
|
+
expect(schema.objectField.properties!.normalField.type).toBe('string');
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it('should handle object properties containing enum properties', () => {
|
|
678
|
+
const schema: PropertiesSchema = {
|
|
679
|
+
objectField: {
|
|
680
|
+
type: 'object',
|
|
681
|
+
properties: {
|
|
682
|
+
enumField: {
|
|
683
|
+
enum: ['option1', 'option2']
|
|
684
|
+
} as EnumSchema,
|
|
685
|
+
normalField: {}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
normalizeProperties(schema);
|
|
691
|
+
|
|
692
|
+
expect(schema.objectField.type).toBe('object');
|
|
693
|
+
const enumField = schema.objectField.properties!.enumField as unknown as EnumSchema;
|
|
694
|
+
expect(enumField.type).toBe('string');
|
|
695
|
+
expect(schema.objectField.properties!.normalField.type).toBe('string');
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it('should handle deeply nested arrays within objects', () => {
|
|
699
|
+
const schema: PropertiesSchema = {
|
|
700
|
+
objectField: {
|
|
701
|
+
type: 'object',
|
|
702
|
+
properties: {
|
|
703
|
+
nestedArray: {
|
|
704
|
+
type: 'array',
|
|
705
|
+
items: {
|
|
706
|
+
type: 'array',
|
|
707
|
+
items: {}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
normalizeProperties(schema);
|
|
715
|
+
|
|
716
|
+
expect(schema.objectField.type).toBe('object');
|
|
717
|
+
const nestedArray = schema.objectField.properties!.nestedArray;
|
|
718
|
+
expect(nestedArray.type).toBe('array');
|
|
719
|
+
const nestedItems = (nestedArray.items as unknown as { type?: string; items?: { type?: string } });
|
|
720
|
+
expect(nestedItems.type).toBe('array');
|
|
721
|
+
// Nested array items are not recursively normalized (only items with properties are normalized)
|
|
722
|
+
// So the nested items remain as {} without type being set
|
|
723
|
+
expect(nestedItems.items).toEqual({});
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
});
|