@amritk/generate-parsers 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +1 -1
  2. package/dist/generators/build-schema.d.ts +0 -1
  3. package/dist/generators/generate-default-from-pattern.d.ts +0 -1
  4. package/dist/generators/generate-discriminated-union-validation.d.ts +0 -1
  5. package/dist/generators/generate-enum-check.d.ts +0 -1
  6. package/dist/generators/generate-files.d.ts +0 -1
  7. package/dist/generators/generate-non-discriminated-union-validation.d.ts +0 -1
  8. package/dist/generators/generate-parser-function.d.ts +3 -3
  9. package/dist/generators/generate-parser-function.js +267 -32
  10. package/dist/generators/generate-schema-checks.d.ts +0 -1
  11. package/dist/generators/generate-strict-assertion.d.ts +0 -1
  12. package/dist/generators/generate-validation-expression.d.ts +0 -1
  13. package/dist/helpers/apply-schema-extensions.d.ts +0 -1
  14. package/dist/helpers/collect-helpers.d.ts +0 -1
  15. package/dist/helpers/collect-imports.d.ts +0 -1
  16. package/dist/helpers/find-discriminator.d.ts +0 -1
  17. package/dist/helpers/get-default-value.d.ts +0 -1
  18. package/dist/helpers/get-discriminator-value.d.ts +0 -1
  19. package/dist/index.d.ts +0 -1
  20. package/dist/types/schema-extensions.d.ts +0 -1
  21. package/package.json +3 -5
  22. package/dist/generators/build-schema.d.ts.map +0 -1
  23. package/dist/generators/generate-default-from-pattern.d.ts.map +0 -1
  24. package/dist/generators/generate-discriminated-union-validation.d.ts.map +0 -1
  25. package/dist/generators/generate-enum-check.d.ts.map +0 -1
  26. package/dist/generators/generate-files.d.ts.map +0 -1
  27. package/dist/generators/generate-non-discriminated-union-validation.d.ts.map +0 -1
  28. package/dist/generators/generate-parser-function.d.ts.map +0 -1
  29. package/dist/generators/generate-schema-checks.d.ts.map +0 -1
  30. package/dist/generators/generate-strict-assertion.d.ts.map +0 -1
  31. package/dist/generators/generate-validation-expression.d.ts.map +0 -1
  32. package/dist/helpers/apply-schema-extensions.d.ts.map +0 -1
  33. package/dist/helpers/collect-helpers.d.ts.map +0 -1
  34. package/dist/helpers/collect-imports.d.ts.map +0 -1
  35. package/dist/helpers/find-discriminator.d.ts.map +0 -1
  36. package/dist/helpers/get-default-value.d.ts.map +0 -1
  37. package/dist/helpers/get-discriminator-value.d.ts.map +0 -1
  38. package/dist/index.d.ts.map +0 -1
  39. package/dist/types/schema-extensions.d.ts.map +0 -1
  40. package/src/generators/build-schema.test.ts +0 -854
  41. package/src/generators/build-schema.ts +0 -147
  42. package/src/generators/generate-default-from-pattern.test.ts +0 -81
  43. package/src/generators/generate-default-from-pattern.ts +0 -39
  44. package/src/generators/generate-discriminated-union-validation.test.ts +0 -84
  45. package/src/generators/generate-discriminated-union-validation.ts +0 -54
  46. package/src/generators/generate-enum-check.test.ts +0 -45
  47. package/src/generators/generate-enum-check.ts +0 -8
  48. package/src/generators/generate-files.test.ts +0 -448
  49. package/src/generators/generate-files.ts +0 -155
  50. package/src/generators/generate-non-discriminated-union-validation.test.ts +0 -74
  51. package/src/generators/generate-non-discriminated-union-validation.ts +0 -40
  52. package/src/generators/generate-parser-function.test.ts +0 -1794
  53. package/src/generators/generate-parser-function.ts +0 -1340
  54. package/src/generators/generate-schema-checks.test.ts +0 -330
  55. package/src/generators/generate-schema-checks.ts +0 -128
  56. package/src/generators/generate-strict-assertion.test.ts +0 -67
  57. package/src/generators/generate-strict-assertion.ts +0 -243
  58. package/src/generators/generate-validation-expression.test.ts +0 -831
  59. package/src/generators/generate-validation-expression.ts +0 -327
  60. package/src/helpers/apply-schema-extensions.test.ts +0 -194
  61. package/src/helpers/apply-schema-extensions.ts +0 -63
  62. package/src/helpers/collect-helpers.test.ts +0 -115
  63. package/src/helpers/collect-helpers.ts +0 -79
  64. package/src/helpers/collect-imports.test.ts +0 -547
  65. package/src/helpers/collect-imports.ts +0 -275
  66. package/src/helpers/find-discriminator.test.ts +0 -88
  67. package/src/helpers/find-discriminator.ts +0 -39
  68. package/src/helpers/get-default-value.test.ts +0 -139
  69. package/src/helpers/get-default-value.ts +0 -95
  70. package/src/helpers/get-discriminator-value.test.ts +0 -83
  71. package/src/helpers/get-discriminator-value.ts +0 -21
  72. package/src/index.ts +0 -1
  73. package/src/types/schema-extensions.ts +0 -35
@@ -1,275 +0,0 @@
1
- import { refToFilename } from '@amritk/helpers/ref-to-filename'
2
- import { refToName } from '@amritk/helpers/ref-to-name'
3
- import { resolveRef } from '@amritk/helpers/resolve-ref'
4
- import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasItems, hasOneOf, hasRef } from '@amritk/helpers/schema-guards'
5
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
6
-
7
- const getImportPathForFilename = (filename: string): string => `./${filename}`
8
-
9
- /**
10
- * Options for controlling how imports are collected.
11
- */
12
- type CollectImportsOptions = {
13
- /**
14
- * When true, only generate type-only imports (no parser function imports).
15
- * Use this when generating types-only files that do not include parser functions.
16
- */
17
- readonly typesOnly?: boolean
18
- /**
19
- * The $ref path of the schema being generated (e.g. `#/$defs/encoding`).
20
- * When provided, any $ref that resolves to the same filename is excluded from
21
- * the import list, preventing a file from importing itself.
22
- */
23
- readonly selfRef?: string | undefined
24
- /**
25
- * The root schema document. When provided, URI refs that cannot be resolved
26
- * within the root schema's $defs are excluded from the import list, preventing
27
- * imports for external schemas that were never generated as files.
28
- */
29
- readonly rootSchema?: Record<string, unknown> | undefined
30
- /**
31
- * Suffix appended to every type name derived from a `$ref`. Must match the
32
- * suffix used when generating the referenced files so imports resolve.
33
- * Defaults to `''` (no suffix).
34
- */
35
- readonly typeSuffix?: string
36
- }
37
-
38
- /**
39
- * Collects all import statements needed for a schema by finding $ref references
40
- * that are directly used in the type definition (from properties, items, etc.).
41
- * Does not include refs from nested schema definitions.
42
- *
43
- * Generates both type imports and parser function imports for each $ref.
44
- * The parser imports allow validators to delegate validation to the referenced
45
- * schema's parser instead of inlining the validation logic.
46
- *
47
- * Also detects when helper functions (isObject) and validators (validateRecord,
48
- * validateArray) are needed based on the schema structure.
49
- *
50
- * @param schema - The JSON Schema to collect imports from
51
- * @param options - Optional settings to control import generation
52
- * @returns An array of import statements
53
- *
54
- * @example
55
- * ```ts
56
- * const schema = {
57
- * type: 'object',
58
- * properties: {
59
- * contact: { $ref: '#/$defs/contact' },
60
- * server: { $ref: '#/$defs/server' }
61
- * }
62
- * }
63
- * const imports = collectImports(schema)
64
- * // imports = [
65
- * // "import { type Contact, parseContact } from './contact';",
66
- * // "import { type Server, parseServer } from './server';"
67
- * // ]
68
- *
69
- * // With typesOnly, only type imports are generated:
70
- * const typeImports = collectImports(schema, { typesOnly: true })
71
- * // typeImports = [
72
- * // "import type { Contact } from './contact';",
73
- * // "import type { Server } from './server';"
74
- * // ]
75
- * ```
76
- */
77
- export const collectImports = (schema: JSONSchema, options?: CollectImportsOptions): string[] => {
78
- const typesOnly = options?.typesOnly === true
79
- const selfFilename = options?.selfRef ? refToFilename(options.selfRef) : undefined
80
- const rootSchema = options?.rootSchema
81
- const typeSuffix = options?.typeSuffix
82
- const refs = new Set<string>()
83
-
84
- const collectRefsFromValue = (value: unknown): void => {
85
- if (typeof value !== 'object' || value === null) {
86
- return
87
- }
88
-
89
- if (Array.isArray(value)) {
90
- for (const item of value) {
91
- collectRefsFromValue(item)
92
- }
93
- return
94
- }
95
-
96
- const record = value as Record<string, unknown>
97
-
98
- // If this is a $ref, add it — but skip:
99
- // - Relative path refs (e.g. /components/messages/foo) which point into example data
100
- // - URI refs with fragments pointing into `properties` (not standalone definitions)
101
- if (hasRef(record)) {
102
- const ref = record.$ref
103
- const isInternal = ref.startsWith('#')
104
- const isUri = ref.startsWith('http://') || ref.startsWith('https://')
105
- const isPropertyFragment = isUri && ref.includes('#/properties/')
106
- if (isInternal || (isUri && !isPropertyFragment)) {
107
- refs.add(ref)
108
- }
109
- return // Don't traverse further into a $ref
110
- }
111
-
112
- // Check if this is an object type with additionalProperties that has a $ref
113
- // This pattern requires validateRecord
114
- if (hasAdditionalProperties(record) && hasRef(record.additionalProperties)) {
115
- collectRefsFromValue(record.additionalProperties)
116
- return
117
- }
118
-
119
- // Check if this is an array type with items that has a $ref
120
- // This pattern requires validateArray
121
- if (record.type === 'array' && hasItems(record) && hasRef(record.items)) {
122
- collectRefsFromValue(record.items)
123
- return
124
- }
125
-
126
- // Traverse nested properties regardless of whether `type` is explicitly set.
127
- if ('properties' in record && typeof record.properties === 'object' && record.properties !== null) {
128
- const props = record.properties as Record<string, unknown>
129
- for (const key in props) {
130
- collectRefsFromValue(props[key])
131
- }
132
- }
133
-
134
- // Traverse into type composition keywords
135
- if (hasOneOf(record)) {
136
- for (const item of record.oneOf) {
137
- collectRefsFromValue(item)
138
- }
139
- }
140
- if (hasAnyOf(record)) {
141
- for (const item of record.anyOf) {
142
- collectRefsFromValue(item)
143
- }
144
- }
145
- if (hasAllOf(record)) {
146
- for (const item of record.allOf) {
147
- collectRefsFromValue(item)
148
- }
149
- }
150
-
151
- // Traverse into array items
152
- if (hasItems(record)) {
153
- collectRefsFromValue(record.items)
154
- }
155
-
156
- // Traverse into additionalProperties
157
- if (hasAdditionalProperties(record)) {
158
- collectRefsFromValue(record.additionalProperties)
159
- }
160
-
161
- // Traverse all patternProperties
162
- if (
163
- 'patternProperties' in record &&
164
- typeof record.patternProperties === 'object' &&
165
- record.patternProperties !== null
166
- ) {
167
- const patternProps = record.patternProperties as Record<string, unknown>
168
- for (const key in patternProps) {
169
- collectRefsFromValue(patternProps[key])
170
- }
171
- }
172
-
173
- // Traverse into if/then/else branches
174
- if ('then' in record) {
175
- collectRefsFromValue(record.then)
176
- }
177
- if ('else' in record) {
178
- collectRefsFromValue(record.else)
179
- }
180
- }
181
-
182
- // Collect refs from root-level $ref
183
- if (typeof schema === 'object' && schema !== null && hasRef(schema)) {
184
- const ref = schema.$ref
185
- const isInternal = ref.startsWith('#')
186
- const isUri = ref.startsWith('http://') || ref.startsWith('https://')
187
- const isPropertyFragment = isUri && ref.includes('#/properties/')
188
- if (isInternal || (isUri && !isPropertyFragment)) {
189
- refs.add(ref)
190
- }
191
- }
192
-
193
- // Collect refs from properties
194
- if (typeof schema === 'object' && schema !== null && 'properties' in schema) {
195
- const properties = schema.properties as Record<string, unknown>
196
- for (const key in properties) {
197
- collectRefsFromValue(properties[key])
198
- }
199
- }
200
-
201
- // Collect refs from root-level additionalProperties
202
- if (typeof schema === 'object' && schema !== null && 'additionalProperties' in schema) {
203
- collectRefsFromValue(schema.additionalProperties)
204
- }
205
-
206
- // Collect refs from root-level patternProperties
207
- if (typeof schema === 'object' && schema !== null && 'patternProperties' in schema) {
208
- const patternProps = schema.patternProperties as Record<string, unknown>
209
- for (const key in patternProps) {
210
- collectRefsFromValue(patternProps[key])
211
- }
212
- }
213
-
214
- // Collect refs from root-level items (when the schema itself is an array type)
215
- if (typeof schema === 'object' && schema !== null && hasItems(schema)) {
216
- collectRefsFromValue(schema.items)
217
- }
218
-
219
- // Collect refs from root-level composition keywords.
220
- if (typeof schema === 'object' && schema !== null && hasOneOf(schema)) {
221
- for (const item of schema.oneOf) {
222
- collectRefsFromValue(item)
223
- }
224
- }
225
- if (typeof schema === 'object' && schema !== null && hasAnyOf(schema)) {
226
- for (const item of schema.anyOf) {
227
- collectRefsFromValue(item)
228
- }
229
- }
230
- if (typeof schema === 'object' && schema !== null && hasAllOf(schema)) {
231
- for (const item of schema.allOf) {
232
- collectRefsFromValue(item)
233
- }
234
- }
235
-
236
- // Collect refs from root-level conditional branches.
237
- if (typeof schema === 'object' && schema !== null && 'if' in schema) {
238
- collectRefsFromValue(schema.if)
239
- }
240
- if (typeof schema === 'object' && schema !== null && 'then' in schema) {
241
- collectRefsFromValue(schema.then)
242
- }
243
- if (typeof schema === 'object' && schema !== null && 'else' in schema) {
244
- collectRefsFromValue(schema.else)
245
- }
246
-
247
- // Convert refs to combined import statements, deduplicating by filename
248
- const importMap = new Map<string, string>()
249
-
250
- for (const ref of refs) {
251
- // For URI refs, skip if the base URI is not resolvable in the root schema.
252
- // This prevents generating imports for external schemas that were never generated as files.
253
- if (rootSchema !== undefined && (ref.startsWith('http://') || ref.startsWith('https://'))) {
254
- if (!resolveRef(ref, rootSchema)) continue
255
- }
256
-
257
- const typeName = refToName(ref, typeSuffix)
258
- const filename = refToFilename(ref)
259
-
260
- // Skip self-referential imports — a file must not import from itself
261
- if (selfFilename !== undefined && filename === selfFilename) {
262
- continue
263
- }
264
-
265
- const importPath = getImportPathForFilename(filename)
266
-
267
- // In types-only mode, omit the parser function import since there is no parser to call
268
- const importStatement = typesOnly
269
- ? `import type { ${typeName} } from '${importPath}';`
270
- : `import { type ${typeName}, parse${typeName}, validate${typeName}Shape } from '${importPath}';`
271
- importMap.set(filename, importStatement)
272
- }
273
-
274
- return Array.from(importMap.values()).sort()
275
- }
@@ -1,88 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { findDiscriminator } from './find-discriminator'
4
-
5
- describe('find-discriminator', () => {
6
- it('finds discriminator when each schema has a unique const value', () => {
7
- const schemas = [
8
- { type: 'object' as const, properties: { kind: { const: 'a' } } },
9
- { type: 'object' as const, properties: { kind: { const: 'b' } } },
10
- { type: 'object' as const, properties: { kind: { const: 'c' } } },
11
- ]
12
- expect(findDiscriminator(schemas)).toBe('kind')
13
- })
14
-
15
- it('finds discriminator when each schema has a single enum value', () => {
16
- const schemas = [
17
- { type: 'object' as const, properties: { type: { enum: ['cat'] } } },
18
- { type: 'object' as const, properties: { type: { enum: ['dog'] } } },
19
- ]
20
- expect(findDiscriminator(schemas)).toBe('type')
21
- })
22
-
23
- it('returns null when schemas share the same const value', () => {
24
- const schemas = [
25
- { type: 'object' as const, properties: { kind: { const: 'a' } } },
26
- { type: 'object' as const, properties: { kind: { const: 'a' } } },
27
- ]
28
- expect(findDiscriminator(schemas)).toBeNull()
29
- })
30
-
31
- it('returns null when schemas have no properties', () => {
32
- const schemas = [{ type: 'object' as const }, { type: 'object' as const }]
33
- expect(findDiscriminator(schemas)).toBeNull()
34
- })
35
-
36
- it('returns null for empty schemas array', () => {
37
- expect(findDiscriminator([])).toBeNull()
38
- })
39
-
40
- it('returns null when schemas are boolean', () => {
41
- expect(findDiscriminator([true, false])).toBeNull()
42
- })
43
-
44
- it('returns null when not all schemas have the discriminator property', () => {
45
- const schemas = [
46
- { type: 'object' as const, properties: { kind: { const: 'a' } } },
47
- { type: 'object' as const, properties: { name: { type: 'string' as const } } },
48
- ]
49
- expect(findDiscriminator(schemas)).toBeNull()
50
- })
51
-
52
- it('returns null when property has multi-value enum', () => {
53
- const schemas = [
54
- { type: 'object' as const, properties: { type: { enum: ['a', 'b'] } } },
55
- { type: 'object' as const, properties: { type: { enum: ['c'] } } },
56
- ]
57
- // Multi-value enum does not count as a discriminator value
58
- expect(findDiscriminator(schemas)).toBeNull()
59
- })
60
-
61
- it('returns null when property schema has no const or enum', () => {
62
- const schemas = [
63
- { type: 'object' as const, properties: { kind: { type: 'string' as const } } },
64
- { type: 'object' as const, properties: { kind: { type: 'string' as const } } },
65
- ]
66
- expect(findDiscriminator(schemas)).toBeNull()
67
- })
68
-
69
- it('prefers the first property that matches across all schemas', () => {
70
- const schemas = [
71
- { type: 'object' as const, properties: { a: { const: 1 }, b: { const: 'x' } } },
72
- { type: 'object' as const, properties: { a: { const: 2 }, b: { const: 'y' } } },
73
- ]
74
- // Both 'a' and 'b' are valid discriminators; returns the first one found
75
- const result = findDiscriminator(schemas)
76
- expect(result === 'a' || result === 'b').toBe(true)
77
- })
78
-
79
- it('ignores schemas without type object', () => {
80
- const schemas = [{ type: 'string' as const }, { type: 'object' as const, properties: { kind: { const: 'a' } } }]
81
- expect(findDiscriminator(schemas)).toBeNull()
82
- })
83
-
84
- it('handles schemas identified as objects by having properties without explicit type', () => {
85
- const schemas = [{ properties: { kind: { const: 'a' } } }, { properties: { kind: { const: 'b' } } }]
86
- expect(findDiscriminator(schemas)).toBe('kind')
87
- })
88
- })
@@ -1,39 +0,0 @@
1
- import { hasConst, hasEnum, hasProperties, isObjectSchema, isSchemaObject } from '@amritk/helpers/schema-guards'
2
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
3
-
4
- /**
5
- * Attempts to find a discriminator property that can be used to distinguish between union types.
6
- * Returns the property name if a suitable discriminator is found, otherwise null.
7
- */
8
- export const findDiscriminator = (schemas: readonly JSONSchema[]): string | null => {
9
- // Look for a property that has different constant/enum values in each schema
10
- const propertyValues = new Map<string, Set<unknown>>()
11
-
12
- for (const schema of schemas) {
13
- if (isObjectSchema(schema) && hasProperties(schema)) {
14
- for (const [key, propSchema] of Object.entries(schema.properties)) {
15
- if (!propertyValues.has(key)) {
16
- propertyValues.set(key, new Set())
17
- }
18
-
19
- // Check if property has a const value
20
- if (isSchemaObject(propSchema) && hasConst(propSchema)) {
21
- propertyValues.get(key)?.add(propSchema.const)
22
- }
23
- // Check if property has a single enum value
24
- else if (isSchemaObject(propSchema) && hasEnum(propSchema) && propSchema.enum.length === 1) {
25
- propertyValues.get(key)?.add(propSchema.enum[0])
26
- }
27
- }
28
- }
29
- }
30
-
31
- // Find a property where each schema has a unique value
32
- for (const [key, values] of propertyValues.entries()) {
33
- if (values.size === schemas.length) {
34
- return key
35
- }
36
- }
37
-
38
- return null
39
- }
@@ -1,139 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { getDefaultValue } from './get-default-value'
4
-
5
- describe('get-default-value', () => {
6
- it('returns undefined for boolean schema', () => {
7
- expect(getDefaultValue(true)).toBe('undefined')
8
- expect(getDefaultValue(false)).toBe('undefined')
9
- })
10
-
11
- it('returns explicit default value when present', () => {
12
- expect(getDefaultValue({ default: 'hello' })).toBe('"hello"')
13
- })
14
-
15
- it('returns explicit default for numeric value', () => {
16
- expect(getDefaultValue({ default: 42 })).toBe('42')
17
- })
18
-
19
- it('returns explicit default for boolean value', () => {
20
- expect(getDefaultValue({ default: true })).toBe('true')
21
- })
22
-
23
- it('returns explicit default for null', () => {
24
- expect(getDefaultValue({ default: null })).toBe('null')
25
- })
26
-
27
- it('returns explicit default for object value', () => {
28
- expect(getDefaultValue({ default: { key: 'value' } })).toBe('{"key":"value"}')
29
- })
30
-
31
- it('returns first enum value when no default', () => {
32
- expect(getDefaultValue({ enum: ['a', 'b', 'c'] })).toBe('"a"')
33
- })
34
-
35
- it('returns first example value when no default or enum', () => {
36
- expect(getDefaultValue({ examples: ['example1', 'example2'] })).toBe('"example1"')
37
- })
38
-
39
- it('returns default from first oneOf schema', () => {
40
- const schema = {
41
- oneOf: [{ type: 'string' as const, default: 'from-oneof' }, { type: 'number' as const }],
42
- }
43
- expect(getDefaultValue(schema)).toBe('"from-oneof"')
44
- })
45
-
46
- it('recurses into oneOf when first schema has no explicit default', () => {
47
- const schema = {
48
- oneOf: [{ type: 'string' as const }, { type: 'number' as const }],
49
- }
50
- expect(getDefaultValue(schema)).toBe('""')
51
- })
52
-
53
- it('returns default from first anyOf schema', () => {
54
- const schema = {
55
- anyOf: [{ type: 'number' as const }],
56
- }
57
- expect(getDefaultValue(schema)).toBe('0')
58
- })
59
-
60
- it('returns default from first allOf schema', () => {
61
- const schema = {
62
- allOf: [{ type: 'boolean' as const }],
63
- }
64
- expect(getDefaultValue(schema)).toBe('false')
65
- })
66
-
67
- it('returns undefined when no type is specified', () => {
68
- expect(getDefaultValue({ description: 'no type' })).toBe('undefined')
69
- })
70
-
71
- it('returns empty string for string type', () => {
72
- expect(getDefaultValue({ type: 'string' })).toBe('""')
73
- })
74
-
75
- it('returns 0 for number type', () => {
76
- expect(getDefaultValue({ type: 'number' })).toBe('0')
77
- })
78
-
79
- it('returns 0 for integer type', () => {
80
- expect(getDefaultValue({ type: 'integer' })).toBe('0')
81
- })
82
-
83
- it('returns false for boolean type', () => {
84
- expect(getDefaultValue({ type: 'boolean' })).toBe('false')
85
- })
86
-
87
- it('returns empty array for array type', () => {
88
- expect(getDefaultValue({ type: 'array' })).toBe('[]')
89
- })
90
-
91
- it('returns empty object for object type', () => {
92
- expect(getDefaultValue({ type: 'object' })).toBe('{}')
93
- })
94
-
95
- it('returns undefined for unknown type', () => {
96
- expect(getDefaultValue({ type: 'null' })).toBe('undefined')
97
- })
98
-
99
- it('uses pattern-based default for string with email pattern', () => {
100
- const schema = { type: 'string' as const, pattern: '^[a-z]+@[a-z]+\\.[a-z]+$' }
101
- expect(getDefaultValue(schema)).toBe('"user@example.com"')
102
- })
103
-
104
- it('falls back to empty string when pattern is not recognized', () => {
105
- const schema = { type: 'string' as const, pattern: '^[a-z]+$' }
106
- expect(getDefaultValue(schema)).toBe('""')
107
- })
108
-
109
- it('prioritizes default over enum', () => {
110
- const schema = { default: 'explicit', enum: ['a', 'b'] }
111
- expect(getDefaultValue(schema)).toBe('"explicit"')
112
- })
113
-
114
- it('prioritizes enum over examples', () => {
115
- const schema = { enum: ['enumVal'], examples: ['exampleVal'] }
116
- expect(getDefaultValue(schema)).toBe('"enumVal"')
117
- })
118
-
119
- it('prioritizes examples over type-based default', () => {
120
- const schema = { type: 'string' as const, examples: ['myExample'] }
121
- expect(getDefaultValue(schema)).toBe('"myExample"')
122
- })
123
-
124
- it('handles empty enum array and falls back', () => {
125
- expect(getDefaultValue({ enum: [], type: 'string' })).toBe('""')
126
- })
127
-
128
- it('handles empty examples array and falls back', () => {
129
- expect(getDefaultValue({ examples: [], type: 'number' })).toBe('0')
130
- })
131
-
132
- it('handles empty oneOf array and falls back', () => {
133
- expect(getDefaultValue({ oneOf: [], type: 'boolean' })).toBe('false')
134
- })
135
-
136
- it('returns 0n for an x-mjst bigint schema', () => {
137
- expect(getDefaultValue({ 'x-mjst': { primitive: 'bigint' } })).toBe('0n')
138
- })
139
- })
@@ -1,95 +0,0 @@
1
- import { getMjstPrimitive } from '@amritk/helpers/mjst-extension'
2
- import {
3
- hasAllOf,
4
- hasAnyOf,
5
- hasDefault,
6
- hasEnum,
7
- hasExamples,
8
- hasOneOf,
9
- hasPattern,
10
- hasType,
11
- isSchemaObject,
12
- } from '@amritk/helpers/schema-guards'
13
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
14
- import { generateDefaultFromPattern } from '#generators/generate-default-from-pattern'
15
-
16
- /**
17
- * Returns the default value for a JSON Schema property.
18
- * Priority order: explicit default > first enum value > first example > union first schema > pattern-based > type-based.
19
- * These defaults ensure that parsing never fails, even with missing data.
20
- */
21
- export const getDefaultValue = (schema: JSONSchema): string => {
22
- if (!isSchemaObject(schema)) {
23
- return 'undefined'
24
- }
25
-
26
- // Explicit default takes highest priority
27
- if (hasDefault(schema)) {
28
- return JSON.stringify(schema.default)
29
- }
30
-
31
- // Use first enum value if available
32
- if (hasEnum(schema) && schema.enum.length > 0) {
33
- return JSON.stringify(schema.enum[0])
34
- }
35
-
36
- // Use first example if available
37
- if (hasExamples(schema) && schema.examples.length > 0) {
38
- return JSON.stringify(schema.examples[0])
39
- }
40
-
41
- // Handle union types - use first schema's default
42
- if (hasOneOf(schema) && schema.oneOf.length > 0) {
43
- const firstSchema = schema.oneOf[0]
44
- if (firstSchema !== undefined) {
45
- return getDefaultValue(firstSchema)
46
- }
47
- }
48
-
49
- if (hasAnyOf(schema) && schema.anyOf.length > 0) {
50
- const firstSchema = schema.anyOf[0]
51
- if (firstSchema !== undefined) {
52
- return getDefaultValue(firstSchema)
53
- }
54
- }
55
-
56
- // Handle allOf - use first schema's default
57
- if (hasAllOf(schema) && schema.allOf.length > 0) {
58
- const firstSchema = schema.allOf[0]
59
- if (firstSchema !== undefined) {
60
- return getDefaultValue(firstSchema)
61
- }
62
- }
63
-
64
- // x-mjst bigint has no native JSON type but a sensible zero default.
65
- if (getMjstPrimitive(schema) === 'bigint') {
66
- return '0n'
67
- }
68
-
69
- if (!hasType(schema)) {
70
- return 'undefined'
71
- }
72
-
73
- switch (schema.type) {
74
- case 'string': {
75
- if (hasPattern(schema)) {
76
- const patternDefault = generateDefaultFromPattern(schema.pattern)
77
- if (patternDefault) {
78
- return patternDefault
79
- }
80
- }
81
- return '""'
82
- }
83
- case 'number':
84
- case 'integer':
85
- return '0'
86
- case 'boolean':
87
- return 'false'
88
- case 'array':
89
- return '[]'
90
- case 'object':
91
- return '{}'
92
- default:
93
- return 'undefined'
94
- }
95
- }