@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.
Files changed (176) hide show
  1. package/README.md +19 -14
  2. package/dist/CredentialFactory.d.ts +345 -0
  3. package/dist/CredentialFactory.d.ts.map +1 -0
  4. package/dist/CredentialFactory.js +381 -0
  5. package/dist/CredentialFactory.js.map +1 -0
  6. package/dist/Schema.d.ts +448 -0
  7. package/dist/Schema.d.ts.map +1 -0
  8. package/dist/Schema.js +506 -0
  9. package/dist/Schema.js.map +1 -0
  10. package/dist/ValidationError.d.ts +70 -0
  11. package/dist/ValidationError.d.ts.map +1 -0
  12. package/dist/ValidationError.js +78 -0
  13. package/dist/ValidationError.js.map +1 -0
  14. package/dist/Validator.d.ts +483 -0
  15. package/dist/Validator.d.ts.map +1 -0
  16. package/dist/Validator.js +570 -0
  17. package/dist/Validator.js.map +1 -0
  18. package/dist/helpers/JsonSchema.d.ts +99 -0
  19. package/dist/helpers/JsonSchema.d.ts.map +1 -0
  20. package/dist/helpers/JsonSchema.js +3 -0
  21. package/dist/helpers/JsonSchema.js.map +1 -0
  22. package/dist/helpers/cleanupAttributes.d.ts +34 -0
  23. package/dist/helpers/cleanupAttributes.d.ts.map +1 -0
  24. package/dist/helpers/cleanupAttributes.js +113 -0
  25. package/dist/helpers/cleanupAttributes.js.map +1 -0
  26. package/dist/helpers/cleanupNulls.d.ts +27 -0
  27. package/dist/helpers/cleanupNulls.d.ts.map +1 -0
  28. package/dist/helpers/cleanupNulls.js +96 -0
  29. package/dist/helpers/cleanupNulls.js.map +1 -0
  30. package/dist/helpers/createSchemasMap.d.ts +67 -0
  31. package/dist/helpers/createSchemasMap.d.ts.map +1 -0
  32. package/dist/helpers/createSchemasMap.js +200 -0
  33. package/dist/helpers/createSchemasMap.js.map +1 -0
  34. package/dist/helpers/getReferenceIds.d.ts +169 -0
  35. package/dist/helpers/getReferenceIds.d.ts.map +1 -0
  36. package/dist/helpers/getReferenceIds.js +241 -0
  37. package/dist/helpers/getReferenceIds.js.map +1 -0
  38. package/dist/helpers/got.d.ts +60 -0
  39. package/dist/helpers/got.d.ts.map +1 -0
  40. package/dist/helpers/got.js +72 -0
  41. package/dist/helpers/got.js.map +1 -0
  42. package/dist/helpers/mapObjectProperties.d.ts +150 -0
  43. package/dist/helpers/mapObjectProperties.d.ts.map +1 -0
  44. package/dist/helpers/mapObjectProperties.js +229 -0
  45. package/dist/helpers/mapObjectProperties.js.map +1 -0
  46. package/dist/helpers/normalizeAttributes.d.ts +213 -0
  47. package/dist/helpers/normalizeAttributes.d.ts.map +1 -0
  48. package/dist/helpers/normalizeAttributes.js +243 -0
  49. package/dist/helpers/normalizeAttributes.js.map +1 -0
  50. package/dist/helpers/normalizeProperties.d.ts +168 -0
  51. package/dist/helpers/normalizeProperties.d.ts.map +1 -0
  52. package/dist/helpers/normalizeProperties.js +223 -0
  53. package/dist/helpers/normalizeProperties.js.map +1 -0
  54. package/dist/helpers/normalizeRequired.d.ts +159 -0
  55. package/dist/helpers/normalizeRequired.d.ts.map +1 -0
  56. package/dist/helpers/normalizeRequired.js +206 -0
  57. package/dist/helpers/normalizeRequired.js.map +1 -0
  58. package/dist/helpers/normalizeType.d.ts +81 -0
  59. package/dist/helpers/normalizeType.d.ts.map +1 -0
  60. package/dist/helpers/normalizeType.js +210 -0
  61. package/dist/helpers/normalizeType.js.map +1 -0
  62. package/dist/helpers/nullifyEmptyValues.d.ts +139 -0
  63. package/dist/helpers/nullifyEmptyValues.d.ts.map +1 -0
  64. package/dist/helpers/nullifyEmptyValues.js +191 -0
  65. package/dist/helpers/nullifyEmptyValues.js.map +1 -0
  66. package/dist/helpers/removeRequiredAndDefault.d.ts +106 -0
  67. package/dist/helpers/removeRequiredAndDefault.d.ts.map +1 -0
  68. package/dist/helpers/removeRequiredAndDefault.js +138 -0
  69. package/dist/helpers/removeRequiredAndDefault.js.map +1 -0
  70. package/dist/helpers/validateId.d.ts +39 -0
  71. package/dist/helpers/validateId.d.ts.map +1 -0
  72. package/dist/helpers/validateId.js +51 -0
  73. package/dist/helpers/validateId.js.map +1 -0
  74. package/dist/index.d.ts +9 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +21 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/ld/documentLoader.d.ts +8 -0
  79. package/dist/ld/documentLoader.d.ts.map +1 -0
  80. package/dist/ld/documentLoader.js +24 -0
  81. package/dist/ld/documentLoader.js.map +1 -0
  82. package/dist/ld/getLinkedDataAttributeType.d.ts +10 -0
  83. package/dist/ld/getLinkedDataAttributeType.d.ts.map +1 -0
  84. package/dist/ld/getLinkedDataAttributeType.js +32 -0
  85. package/dist/ld/getLinkedDataAttributeType.js.map +1 -0
  86. package/dist/ld/getLinkedDataContext.d.ts +19 -0
  87. package/dist/ld/getLinkedDataContext.d.ts.map +1 -0
  88. package/dist/ld/getLinkedDataContext.js +50 -0
  89. package/dist/ld/getLinkedDataContext.js.map +1 -0
  90. package/eslint.config.mjs +32 -52
  91. package/examples/credentials/createAccountCredential.ts +27 -0
  92. package/examples/credentials/createMineSweeperScoreCredential.ts +115 -0
  93. package/examples/index.ts +7 -0
  94. package/examples/schemas/FavoriteItemSchema.ts +27 -0
  95. package/examples/{Preferences.yaml → schemas/Preferences.yaml} +2 -0
  96. package/examples/schemas/PreferencesSchema.ts +29 -0
  97. package/examples/schemas/ProfileSchema.ts +91 -0
  98. package/examples/schemas/Status.yaml +3 -0
  99. package/examples/schemas/StatusSchema.ts +12 -0
  100. package/jest.config.mjs +5 -0
  101. package/package.json +27 -20
  102. package/src/CredentialFactory.ts +392 -0
  103. package/src/Schema.ts +583 -0
  104. package/src/ValidationError.ts +90 -0
  105. package/src/Validator.ts +603 -0
  106. package/src/__tests__/CredentialFactory.test.ts +588 -0
  107. package/src/__tests__/Schema.test.ts +371 -0
  108. package/src/__tests__/ValidationError.test.ts +235 -0
  109. package/src/__tests__/Validator.test.ts +787 -0
  110. package/src/helpers/JsonSchema.ts +119 -0
  111. package/src/helpers/__tests__/cleanupAttributes.test.ts +943 -0
  112. package/src/helpers/__tests__/cleanupNulls.test.ts +772 -0
  113. package/src/helpers/__tests__/createSchemasMap.test.ts +238 -0
  114. package/src/helpers/__tests__/getReferenceIds.test.ts +975 -0
  115. package/src/helpers/__tests__/got.test.ts +193 -0
  116. package/src/helpers/__tests__/mapObjectProperties.test.ts +1126 -0
  117. package/src/helpers/__tests__/normalizeAttributes.test.ts +1435 -0
  118. package/src/helpers/__tests__/normalizeProperties.test.ts +727 -0
  119. package/src/helpers/__tests__/normalizeRequired.test.ts +669 -0
  120. package/src/helpers/__tests__/normalizeType.test.ts +772 -0
  121. package/src/helpers/__tests__/nullifyEmptyValues.test.ts +735 -0
  122. package/src/helpers/__tests__/removeRequiredAndDefault.test.ts +734 -0
  123. package/src/helpers/__tests__/validateId.test.ts +118 -0
  124. package/src/helpers/cleanupAttributes.ts +151 -0
  125. package/src/helpers/cleanupNulls.ts +106 -0
  126. package/src/helpers/createSchemasMap.ts +212 -0
  127. package/src/helpers/getReferenceIds.ts +273 -0
  128. package/src/helpers/got.ts +73 -0
  129. package/src/helpers/mapObjectProperties.ts +272 -0
  130. package/src/helpers/normalizeAttributes.ts +247 -0
  131. package/src/helpers/normalizeProperties.ts +249 -0
  132. package/src/helpers/normalizeRequired.ts +233 -0
  133. package/src/helpers/normalizeType.ts +235 -0
  134. package/src/helpers/nullifyEmptyValues.ts +207 -0
  135. package/src/helpers/removeRequiredAndDefault.ts +151 -0
  136. package/src/helpers/validateId.ts +53 -0
  137. package/src/index.ts +17 -0
  138. package/src/ld/__tests__/documentLoader.test.ts +57 -0
  139. package/src/ld/__tests__/getLinkedDataAttributeType.test.ts +212 -0
  140. package/src/ld/__tests__/getLinkedDataContext.test.ts +378 -0
  141. package/src/ld/documentLoader.ts +28 -0
  142. package/src/ld/getLinkedDataAttributeType.ts +46 -0
  143. package/src/ld/getLinkedDataContext.ts +80 -0
  144. package/tsconfig.json +27 -0
  145. package/types/credentials-context.d.ts +14 -0
  146. package/types/security-context.d.ts +6 -0
  147. package/examples/Status.yaml +0 -3
  148. package/examples/createAccountCredential.js +0 -27
  149. package/examples/createMineSweeperScoreCredential.js +0 -63
  150. package/examples/index.js +0 -9
  151. package/src/CredentialFactory.js +0 -67
  152. package/src/CredentialFactory.spec.js +0 -131
  153. package/src/Schema.js +0 -104
  154. package/src/Schema.spec.js +0 -172
  155. package/src/ValidationError.js +0 -31
  156. package/src/Validator.js +0 -128
  157. package/src/Validator.spec.js +0 -355
  158. package/src/helpers/cleanupAttributes.js +0 -71
  159. package/src/helpers/cleanupNulls.js +0 -42
  160. package/src/helpers/getReferenceIds.js +0 -71
  161. package/src/helpers/mapObject.js +0 -65
  162. package/src/helpers/normalizeAttributes.js +0 -28
  163. package/src/helpers/normalizeProperties.js +0 -61
  164. package/src/helpers/normalizeRequired.js +0 -37
  165. package/src/helpers/normalizeType.js +0 -41
  166. package/src/helpers/nullifyEmptyValues.js +0 -57
  167. package/src/helpers/removeRequiredAndDefault.js +0 -30
  168. package/src/helpers/validateId.js +0 -19
  169. package/src/index.d.ts +0 -25
  170. package/src/index.js +0 -8
  171. package/src/ld/documentLoader.js +0 -25
  172. package/src/ld/documentLoader.spec.js +0 -12
  173. package/src/ld/getLinkedDataContext.js +0 -63
  174. package/src/ld/getLinkedDataType.js +0 -38
  175. /package/examples/{FavoriteItem.yaml → schemas/FavoriteItem.yaml} +0 -0
  176. /package/examples/{Profile.yaml → schemas/Profile.yaml} +0 -0
@@ -0,0 +1,247 @@
1
+ import { get, isUndefined } from 'lodash';
2
+
3
+ import normalizeType from './normalizeType';
4
+ import mapObjectProperties from './mapObjectProperties';
5
+ import type { TargetObject, JsonSchema, JsonSchemasMap, PropertySchema } from './JsonSchema';
6
+
7
+ /**
8
+ * Normalizes object attribute values based on a JSON Schema definition.
9
+ *
10
+ * ## Intent
11
+ *
12
+ * This function ensures that object properties conform to their schema definitions by:
13
+ * 1. Setting default values for properties that are undefined (but not null)
14
+ * 2. Normalizing existing values to match their schema-defined types (e.g., converting
15
+ * string "123" to number 123, or string "true" to boolean true)
16
+ *
17
+ * The function operates recursively, processing nested objects, arrays, and referenced
18
+ * schemas ($ref) to ensure all properties throughout the object tree are normalized
19
+ * according to their respective schema definitions.
20
+ *
21
+ * This is particularly useful in data processing pipelines where data may come from
22
+ * external sources (forms, APIs, databases) with inconsistent types, but needs to be
23
+ * normalized before validation or further processing.
24
+ *
25
+ * ## Use Cases
26
+ *
27
+ * 1. **Form data processing**: HTML forms submit all values as strings. This function
28
+ * converts them to their expected types (numbers, booleans) based on schema definitions
29
+ * and fills in default values for missing fields.
30
+ *
31
+ * 2. **API response normalization**: When consuming APIs that return loosely-typed data
32
+ * (e.g., numbers as strings, booleans as strings), this function ensures values match
33
+ * the expected schema types before validation or business logic processing.
34
+ *
35
+ * 3. **Configuration object initialization**: Setting default values and normalizing types
36
+ * for configuration objects based on their schema definitions, ensuring consistent
37
+ * structure and types throughout the application.
38
+ *
39
+ * 4. **Data migration and transformation**: Normalizing data structures during migration
40
+ * or transformation processes where source data may have inconsistent types but target
41
+ * schema requires specific types.
42
+ *
43
+ * 5. **Pre-validation normalization**: Preparing objects for schema validation by ensuring
44
+ * types are correct and defaults are applied, reducing validation errors and improving
45
+ * data quality.
46
+ *
47
+ * ## Behavior
48
+ *
49
+ * - **Default values**: Properties that are `undefined` will be set to their schema-defined
50
+ * default value (if one exists). Properties that are `null` are left as `null` and will
51
+ * not receive default values. Default values are also normalized according to their type
52
+ * (e.g., a default string "123" with type "number" will be converted to the number 123).
53
+ *
54
+ * - **Type normalization**: Properties with existing values (including default values that
55
+ * were just set) are normalized to match their schema type using `normalizeType`. This
56
+ * includes converting strings to numbers/booleans where appropriate, while preserving the
57
+ * original value if conversion is not possible.
58
+ *
59
+ * - **Recursive processing**: The function processes nested objects, arrays, and schema
60
+ * references ($ref) recursively, ensuring all nested properties are normalized.
61
+ *
62
+ * - **Non-destructive**: The function mutates the input object in place. If you need to
63
+ * preserve the original, create a deep copy before calling this function.
64
+ *
65
+ * ## Examples
66
+ *
67
+ * ### Basic Usage: Default Values and Type Normalization
68
+ * ```typescript
69
+ * import Schema from './Schema';
70
+ * import normalizeAttributes from './normalizeAttributes';
71
+ *
72
+ * const schema = new Schema({
73
+ * name: { type: 'string', default: 'Anonymous' },
74
+ * age: { type: 'number' },
75
+ * isActive: { type: 'boolean', default: false }
76
+ * }, 'user-schema');
77
+ *
78
+ * const user = {
79
+ * age: '25' // string that should be a number
80
+ * };
81
+ *
82
+ * normalizeAttributes(user, schema.jsonSchema, {});
83
+ *
84
+ * // Result:
85
+ * // {
86
+ * // name: 'Anonymous', // default value applied
87
+ * // age: 25, // string converted to number
88
+ * // isActive: false // default value applied
89
+ * // }
90
+ * ```
91
+ *
92
+ * ### Nested Objects
93
+ * ```typescript
94
+ * const schema = new Schema({
95
+ * address: {
96
+ * type: 'object',
97
+ * properties: {
98
+ * street: { type: 'string', default: 'Unknown' },
99
+ * zipCode: { type: 'number' }
100
+ * }
101
+ * }
102
+ * }, 'profile-schema');
103
+ *
104
+ * const profile = {
105
+ * address: {
106
+ * zipCode: '12345' // string that should be a number
107
+ * }
108
+ * };
109
+ *
110
+ * normalizeAttributes(profile, schema.jsonSchema, {});
111
+ *
112
+ * // Result:
113
+ * // {
114
+ * // address: {
115
+ * // street: 'Unknown', // default value applied
116
+ * // zipCode: 12345 // string converted to number
117
+ * // }
118
+ * // }
119
+ * ```
120
+ *
121
+ * ### Arrays with Schema References
122
+ * ```typescript
123
+ * const itemSchema = new Schema({
124
+ * id: { type: 'number' },
125
+ * name: { type: 'string', default: 'Unnamed' }
126
+ * }, 'item-schema');
127
+ *
128
+ * const schema = new Schema({
129
+ * items: {
130
+ * type: 'array',
131
+ * items: { $ref: 'item-schema' }
132
+ * }
133
+ * }, 'collection-schema');
134
+ *
135
+ * const collection = {
136
+ * items: [
137
+ * { id: '1' }, // id is a string, should be number
138
+ * { id: '2', name: 'Item 2' }
139
+ * ]
140
+ * };
141
+ *
142
+ * const schemasMap = {
143
+ * 'item-schema': itemSchema.jsonSchema
144
+ * };
145
+ *
146
+ * normalizeAttributes(collection, schema.jsonSchema, schemasMap);
147
+ *
148
+ * // Result:
149
+ * // {
150
+ * // items: [
151
+ * // { id: 1, name: 'Unnamed' }, // id normalized, default name applied
152
+ * // { id: 2, name: 'Item 2' } // id normalized, existing name preserved
153
+ * // ]
154
+ * // }
155
+ * ```
156
+ *
157
+ * ### Boolean Normalization
158
+ * ```typescript
159
+ * const schema = new Schema({
160
+ * enabled: { type: 'boolean', default: false },
161
+ * verified: { type: 'boolean' }
162
+ * }, 'settings-schema');
163
+ *
164
+ * const settings = {
165
+ * verified: 'yes' // string that should be boolean
166
+ * };
167
+ *
168
+ * normalizeAttributes(settings, schema.jsonSchema, {});
169
+ *
170
+ * // Result:
171
+ * // {
172
+ * // enabled: false, // default value applied
173
+ * // verified: true // string "yes" converted to boolean true
174
+ * // }
175
+ * ```
176
+ *
177
+ * ### Handling Null Values
178
+ * ```typescript
179
+ * const schema = new Schema({
180
+ * optionalField: { type: 'string', default: 'default-value' }
181
+ * }, 'test-schema');
182
+ *
183
+ * const obj1 = {}; // undefined → default applied
184
+ * const obj2 = { optionalField: null }; // null → no default applied
185
+ *
186
+ * normalizeAttributes(obj1, schema.jsonSchema, {});
187
+ * normalizeAttributes(obj2, schema.jsonSchema, {});
188
+ *
189
+ * // obj1: { optionalField: 'default-value' }
190
+ * // obj2: { optionalField: null } // null preserved, default not applied
191
+ * ```
192
+ *
193
+ * ### Default Value Normalization
194
+ * ```typescript
195
+ * const schema = new Schema({
196
+ * count: { type: 'number', default: '42' }, // default is string, type is number
197
+ * enabled: { type: 'boolean', default: 'true' } // default is string, type is boolean
198
+ * }, 'config-schema');
199
+ *
200
+ * const config = {};
201
+ *
202
+ * normalizeAttributes(config, schema.jsonSchema, {});
203
+ *
204
+ * // Result:
205
+ * // {
206
+ * // count: 42, // default string "42" normalized to number
207
+ * // enabled: true // default string "true" normalized to boolean
208
+ * // }
209
+ * ```
210
+ *
211
+ * @param object - The target object to normalize (mutated in place)
212
+ * @param jsonSchema - The JSON Schema definition describing the object structure
213
+ * @param jsonSchemasMap - Map of schema IDs to schema definitions, used for resolving $ref references
214
+ * @returns void (mutates the input object)
215
+ */
216
+ const normalizeAttributes = (object: TargetObject, jsonSchema: JsonSchema, jsonSchemasMap: JsonSchemasMap) => {
217
+ /** Callback to normalize value based on property type defined in schema */
218
+ const callback = (propertyName: string, propertySchema: PropertySchema, object: TargetObject) => {
219
+ let value = object[propertyName];
220
+
221
+ const type = get(propertySchema, 'type');
222
+ const defaultValue = get(propertySchema, 'default');
223
+
224
+ const hasValue = !isUndefined(value);
225
+ const hasDefaultValue = !isUndefined(defaultValue);
226
+ const shouldSetDefaultValue = hasDefaultValue && !hasValue;
227
+
228
+ // Set default value if property is undefined and default exists
229
+ if (shouldSetDefaultValue) {
230
+ object[propertyName] = defaultValue;
231
+ value = defaultValue; // Update value reference for normalization
232
+ }
233
+
234
+ const hasType = !!type;
235
+ const hasValueAfterDefault = !isUndefined(value);
236
+ const shouldNormalizeValue = hasType && hasValueAfterDefault;
237
+
238
+ // Normalize the current value (original or default) if type is defined
239
+ if (shouldNormalizeValue) {
240
+ object[propertyName] = normalizeType(type, value);
241
+ }
242
+ };
243
+
244
+ mapObjectProperties(object, jsonSchema, jsonSchemasMap, callback);
245
+ };
246
+
247
+ export default normalizeAttributes;
@@ -0,0 +1,249 @@
1
+ import { get, set, isUndefined } from 'lodash';
2
+
3
+ import type {
4
+ EnumSchema,
5
+ PropertiesSchema,
6
+ ObjectPropertySchema,
7
+ ReferencePropertySchema,
8
+ ArrayPropertySchema
9
+ } from './JsonSchema';
10
+
11
+ /**
12
+ * Normalizes JSON schema properties by ensuring all properties have an explicit `type` attribute.
13
+ *
14
+ * **Intent:**
15
+ * This function transforms schemas that may have implicit or missing type information into
16
+ * fully normalized schemas with explicit types. It recursively processes nested structures
17
+ * (objects and arrays) to ensure type consistency throughout the schema hierarchy.
18
+ *
19
+ * **Use Cases:**
20
+ * - **Schema Validation Preparation**: Ensures schemas are ready for validation where type
21
+ * information is required by validators or processing tools
22
+ * - **External Schema Normalization**: Normalizes schemas imported from external sources
23
+ * (e.g., OpenAPI specs, YAML schemas) that may omit type information
24
+ * - **Type Inference**: Automatically infers types from structural hints (e.g., presence of
25
+ * `properties` implies `object`, presence of `items` implies `array`)
26
+ * - **Schema Consistency**: Guarantees consistent schema structure before further processing
27
+ * or transformation
28
+ * - **Default Type Assignment**: Provides sensible defaults (e.g., `string` for primitives)
29
+ * when type cannot be inferred
30
+ *
31
+ * **Behavior:**
32
+ * - **Enum Schemas**: Sets `type` to `'string'` if missing (preserves existing type if present)
33
+ * - **Reference Properties**: Skips `$ref` properties (they don't need type normalization)
34
+ * - **Type Inference Priority** (when type is missing):
35
+ * 1. If property has `properties` → sets `type: 'object'`
36
+ * 2. Else if property has `items` → sets `type: 'array'`
37
+ * 3. Else → sets `type: 'string'` (default)
38
+ * - **Object Properties**:
39
+ * - Infers `type: 'object'` from presence of `properties` (if type not already set)
40
+ * - Creates empty `properties: {}` if `type: 'object'` but no properties exist
41
+ * - Recursively normalizes nested object properties
42
+ * - Note: If both `properties` and `items` exist, `properties` takes precedence
43
+ * - **Array Properties**:
44
+ * - Infers `type: 'array'` from presence of `items` (if type not already set)
45
+ * - Sets default `items: { type: 'string' }` if array has no items
46
+ * - Normalizes item schemas: sets `type: 'object'` if items have `properties` (not undefined)
47
+ * - `properties: null` → treated as having properties (sets type to 'object')
48
+ * - `properties: undefined` → treated as not having properties (no type set)
49
+ * - Empty object `{}` → treated as not having properties (no type set)
50
+ * - Recursively normalizes nested properties within array items
51
+ * - **Primitive Properties**: Sets `type: 'string'` as default when no type, items, or properties exist
52
+ * - **Existing Types**: Never overrides existing type values (even if structure suggests different type)
53
+ * - **Edge Cases**:
54
+ * - Empty schemas (`{}`) are handled gracefully
55
+ * - Properties with conflicting structure (e.g., `type: 'object'` with `items`) are normalized
56
+ * according to the explicit type, ignoring conflicting structural hints
57
+ *
58
+ * **Examples:**
59
+ *
60
+ * @example Enum schema normalization
61
+ * ```typescript
62
+ * const schema: EnumSchema = { enum: ['red', 'green', 'blue'] };
63
+ * normalizeProperties(schema);
64
+ * // Result: { enum: ['red', 'green', 'blue'], type: 'string' }
65
+ * ```
66
+ *
67
+ * @example Object type inference
68
+ * ```typescript
69
+ * const schema: PropertiesSchema = {
70
+ * user: {
71
+ * properties: {
72
+ * name: {}
73
+ * }
74
+ * }
75
+ * };
76
+ * normalizeProperties(schema);
77
+ * // Result:
78
+ * // {
79
+ * // user: {
80
+ * // type: 'object',
81
+ * // properties: {
82
+ * // name: { type: 'string' }
83
+ * // }
84
+ * // }
85
+ * // }
86
+ * ```
87
+ *
88
+ * @example Array type inference
89
+ * ```typescript
90
+ * const schema: PropertiesSchema = {
91
+ * tags: {
92
+ * items: { type: 'string' }
93
+ * }
94
+ * };
95
+ * normalizeProperties(schema);
96
+ * // Result:
97
+ * // {
98
+ * // tags: {
99
+ * // type: 'array',
100
+ * // items: { type: 'string' }
101
+ * // }
102
+ * // }
103
+ * ```
104
+ *
105
+ * @example Complex nested structure
106
+ * ```typescript
107
+ * const schema: PropertiesSchema = {
108
+ * profile: {
109
+ * properties: {
110
+ * name: {},
111
+ * addresses: {
112
+ * items: {
113
+ * properties: {
114
+ * street: {},
115
+ * city: {}
116
+ * }
117
+ * }
118
+ * }
119
+ * }
120
+ * }
121
+ * };
122
+ * normalizeProperties(schema);
123
+ * // Result:
124
+ * // {
125
+ * // profile: {
126
+ * // type: 'object',
127
+ * // properties: {
128
+ * // name: { type: 'string' },
129
+ * // addresses: {
130
+ * // type: 'array',
131
+ * // items: {
132
+ * // type: 'object',
133
+ * // properties: {
134
+ * // street: { type: 'string' },
135
+ * // city: { type: 'string' }
136
+ * // }
137
+ * // }
138
+ * // }
139
+ * // }
140
+ * // }
141
+ * // }
142
+ * ```
143
+ *
144
+ * @example Reference properties are skipped
145
+ * ```typescript
146
+ * const schema: PropertiesSchema = {
147
+ * refField: { $ref: '#/definitions/User' },
148
+ * normalField: {}
149
+ * };
150
+ * normalizeProperties(schema);
151
+ * // Result:
152
+ * // {
153
+ * // refField: { $ref: '#/definitions/User' }, // Unchanged
154
+ * // normalField: { type: 'string' }
155
+ * // }
156
+ * ```
157
+ *
158
+ * @example Default type assignment
159
+ * ```typescript
160
+ * const schema: PropertiesSchema = {
161
+ * title: {},
162
+ * count: { type: 'number' }
163
+ * };
164
+ * normalizeProperties(schema);
165
+ * // Result:
166
+ * // {
167
+ * // title: { type: 'string' }, // Default assigned
168
+ * // count: { type: 'number' } // Existing preserved
169
+ * // }
170
+ * ```
171
+ *
172
+ * @param schema - The schema to normalize (either an EnumSchema or PropertiesSchema)
173
+ * @modifies The schema object is mutated in place with normalized types
174
+ */
175
+ const normalizeProperties = (schema: EnumSchema | PropertiesSchema) => {
176
+ const { enum: isEnum } = (schema as EnumSchema);
177
+
178
+ if (isEnum) {
179
+ const enumSchema = (schema as EnumSchema);
180
+ enumSchema.type = enumSchema.type || 'string';
181
+
182
+ return;
183
+ }
184
+
185
+ const properties = (schema as PropertiesSchema);
186
+
187
+ for (const name in properties) {
188
+ const property = properties[name];
189
+
190
+ const { $ref: isRef } = (property as ReferencePropertySchema);
191
+
192
+ if (isRef) {
193
+ continue;
194
+ }
195
+
196
+ const hasType = !!get(property, 'type');
197
+ const hasItems = !!get(property, 'items');
198
+ const hasProperties = !!get(property, 'properties');
199
+
200
+ if (!hasType) {
201
+ if (hasProperties) {
202
+ set(property, 'type', 'object');
203
+
204
+ } else if (hasItems) {
205
+ set(property, 'type', 'array');
206
+
207
+ } else {
208
+ set(property, 'type', 'string');
209
+
210
+ }
211
+ }
212
+
213
+ const type = get(property, 'type');
214
+
215
+ const isObject = type === 'object';
216
+
217
+ if (isObject) {
218
+ if (!hasProperties) {
219
+ (property as ObjectPropertySchema).properties = {};
220
+ }
221
+
222
+ // istanbul ignore next - unreachable defensive code: properties is always set to {} above if missing
223
+ normalizeProperties((property as ObjectPropertySchema).properties || {});
224
+ }
225
+
226
+ const isArray = type === 'array';
227
+
228
+ if (isArray) {
229
+ if (hasItems) {
230
+ // istanbul ignore next - unreachable defensive code: if items is undefined, hasItems would be false
231
+ const { items = {} } = (property as ArrayPropertySchema);
232
+
233
+ const isItemObject = !isUndefined((items as ObjectPropertySchema).properties);
234
+
235
+ if (isItemObject) {
236
+ set(items, 'type', 'object');
237
+
238
+ normalizeProperties((items as ObjectPropertySchema).properties || {});
239
+ }
240
+
241
+ } else {
242
+ (property as ArrayPropertySchema).items = { type: 'string' };
243
+
244
+ }
245
+ }
246
+ }
247
+ };
248
+
249
+ export default normalizeProperties;