@amritk/generate-validators 0.5.1 → 0.7.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.
@@ -1,163 +0,0 @@
1
- import { validate } from '@scalar/openapi-parser'
2
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
3
- import { describe, expect, it } from 'vitest'
4
-
5
- import { buildValidatorSchema } from './build-schema'
6
-
7
- describe('build-schema', () => {
8
- it('generates a validator file for the root schema', async () => {
9
- const schema: JSONSchema = {
10
- type: 'object',
11
- properties: { title: { type: 'string' } },
12
- required: ['title'],
13
- }
14
-
15
- const files = await buildValidatorSchema(schema, 'Document')
16
- const filenames = files.map((f) => f.filename)
17
-
18
- expect(filenames).toContain('document.ts')
19
- expect(filenames).toContain('validation-result.ts')
20
- expect(filenames).toContain('index.ts')
21
- })
22
-
23
- it('generates a file per $ref definition', async () => {
24
- const schema: JSONSchema = {
25
- type: 'object',
26
- properties: {
27
- info: { $ref: '#/$defs/info' },
28
- },
29
- $defs: {
30
- info: {
31
- type: 'object',
32
- properties: { title: { type: 'string' } },
33
- required: ['title'],
34
- },
35
- },
36
- }
37
-
38
- const files = await buildValidatorSchema(schema, 'Document')
39
- const filenames = files.map((f) => f.filename)
40
-
41
- expect(filenames).toContain('document.ts')
42
- expect(filenames).toContain('info.ts')
43
- })
44
-
45
- it('generated document.ts exports a validateDocument function', async () => {
46
- const schema: JSONSchema = {
47
- type: 'object',
48
- properties: { title: { type: 'string' } },
49
- required: ['title'],
50
- }
51
-
52
- const files = await buildValidatorSchema(schema, 'Document')
53
- const documentFile = files.find((f) => f.filename === 'document.ts')
54
-
55
- expect(documentFile?.content).toContain('export const validateDocument')
56
- expect(documentFile?.content).toContain('ValidationResult')
57
- })
58
-
59
- it('generated file imports the ref validator for $ref properties', async () => {
60
- const schema: JSONSchema = {
61
- type: 'object',
62
- properties: {
63
- info: { $ref: '#/$defs/info' },
64
- },
65
- $defs: {
66
- info: {
67
- type: 'object',
68
- properties: { title: { type: 'string' } },
69
- },
70
- },
71
- }
72
-
73
- const files = await buildValidatorSchema(schema, 'Document')
74
- const documentFile = files.find((f) => f.filename === 'document.ts')
75
-
76
- expect(documentFile?.content).toContain("from './info'")
77
- expect(documentFile?.content).toContain('validateInfo')
78
- })
79
-
80
- it('generates a valid index.ts with re-exports', async () => {
81
- const schema: JSONSchema = {
82
- type: 'object',
83
- properties: { title: { type: 'string' } },
84
- }
85
-
86
- const files = await buildValidatorSchema(schema, 'Document')
87
- const indexFile = files.find((f) => f.filename === 'index.ts')
88
-
89
- expect(indexFile?.content).toContain("from './document'")
90
- expect(indexFile?.content).toContain('validateDocument')
91
- })
92
-
93
- it('does not generate a file named validation-result for a schema ref', async () => {
94
- const schema: JSONSchema = {
95
- type: 'object',
96
- properties: {
97
- result: { $ref: '#/$defs/validation-result' },
98
- },
99
- $defs: {
100
- 'validation-result': { type: 'object' },
101
- },
102
- }
103
-
104
- const files = await buildValidatorSchema(schema, 'Document')
105
- // Should still have exactly one validation-result.ts (the runtime contract)
106
- const vrFiles = files.filter((f) => f.filename === 'validation-result.ts')
107
- expect(vrFiles).toHaveLength(1)
108
- expect(vrFiles[0]?.content).toContain('export type ValidationResult')
109
- })
110
-
111
- it('produces generated validators that agree with @scalar/openapi-parser on a valid document', async () => {
112
- // Build validators for a minimal OpenAPI-like schema
113
- const schema: JSONSchema = {
114
- type: 'object',
115
- properties: {
116
- openapi: { type: 'string' },
117
- info: { $ref: '#/$defs/info' },
118
- },
119
- required: ['openapi', 'info'],
120
- $defs: {
121
- info: {
122
- type: 'object',
123
- properties: {
124
- title: { type: 'string' },
125
- version: { type: 'string' },
126
- },
127
- required: ['title', 'version'],
128
- },
129
- },
130
- }
131
-
132
- const files = await buildValidatorSchema(schema, 'Document')
133
-
134
- // Sanity-check generated code shape
135
- const documentFile = files.find((f) => f.filename === 'document.ts')
136
- const infoFile = files.find((f) => f.filename === 'info.ts')
137
-
138
- expect(documentFile?.content).toContain('validateDocument')
139
- expect(infoFile?.content).toContain('validateInfo')
140
-
141
- // Cross-check: @scalar/openapi-parser says a complete document is valid
142
- const validDoc = { openapi: '3.1.0', info: { title: 'API', version: '1.0' }, paths: {} }
143
- const refResult = await validate(validDoc)
144
- expect(refResult.valid).toBe(true)
145
-
146
- // Cross-check: @scalar/openapi-parser says a document missing info.title is invalid
147
- const invalidDoc = { openapi: '3.1.0', info: { version: '1.0' }, paths: {} }
148
- const refInvalid = await validate(invalidDoc)
149
- expect(refInvalid.valid).toBe(false)
150
- expect(refInvalid.errors.some((e) => e.message.includes('title'))).toBe(true)
151
- })
152
-
153
- it('emits a validation-result.ts with the runtime ValidationResult/ValidationError types', async () => {
154
- const schema: JSONSchema = { type: 'object' }
155
- const files = await buildValidatorSchema(schema, 'Doc')
156
- const vrFile = files.find((f) => f.filename === 'validation-result.ts')
157
-
158
- expect(vrFile?.content).toContain('export type ValidationError')
159
- expect(vrFile?.content).toContain('export type ValidationResult')
160
- expect(vrFile?.content).toContain('message: string')
161
- expect(vrFile?.content).toContain('path: string')
162
- })
163
- })
@@ -1,81 +0,0 @@
1
- import { generateIndexBarrel } from '@amritk/helpers/generate-index-barrel'
2
- import { walkRefGraph } from '@amritk/helpers/walk-ref-graph'
3
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
4
-
5
- import { generateValidatorFile } from './generate-files'
6
-
7
- /**
8
- * Represents a generated TypeScript file with its filename and content.
9
- */
10
- export type GeneratedFile = {
11
- filename: string
12
- content: string
13
- }
14
-
15
- const VALIDATION_RESULT_CONTENT = `/**
16
- * A single validation error with a human-readable message and a JSON Pointer
17
- * path indicating where in the document the error occurred.
18
- */
19
- export type ValidationError = {
20
- message: string
21
- path: string
22
- }
23
-
24
- /**
25
- * The result of a generated validator function.
26
- * Returns \`true\` when the input is valid, or an object with \`valid: false\`
27
- * and a list of errors when it is not.
28
- */
29
- export type ValidationResult = true | { valid: false; errors: ValidationError[] }
30
- `
31
-
32
- /**
33
- * Builds all TypeScript validator files from a JSON Schema by traversing all
34
- * `$ref` / `$dynamicRef` references recursively (via the shared
35
- * `@amritk/helpers/walk-ref-graph` walker).
36
- *
37
- * Each generated file exports:
38
- * - A TypeScript type definition
39
- * - A `validateFoo(input: unknown, _path?: string): ValidationResult` function
40
- *
41
- * A `validation-result.ts` file containing the `ValidationResult` and `ValidationError`
42
- * runtime contract is always emitted. An `index.ts` re-exports everything.
43
- *
44
- * @param rootSchema - The root JSON Schema to build from
45
- * @param rootTypeName - The name for the root type (e.g. "Document")
46
- * @returns An array of generated TypeScript files
47
- *
48
- * @example
49
- * ```typescript
50
- * const files = await buildValidatorSchema(schema, 'Document')
51
- * // files → [{ filename: 'document.ts', content: '...' }, { filename: 'info.ts', ... }, ...]
52
- * ```
53
- */
54
- export const buildValidatorSchema = async (
55
- rootSchema: JSONSchema,
56
- rootTypeName: string,
57
- typeSuffix = '',
58
- ): Promise<GeneratedFile[]> => {
59
- const files: GeneratedFile[] = []
60
-
61
- walkRefGraph(rootSchema, rootTypeName, { typeSuffix }, (node) => {
62
- // `validation-result` and `index` are reserved output filenames, so never
63
- // let a definition of either name overwrite them.
64
- if (node.filename === 'validation-result' || node.filename === 'index') return
65
-
66
- const content = generateValidatorFile(node.schema, node.typeName, {
67
- rootSchema: node.rootSchema,
68
- typeSuffix,
69
- ...(node.ref !== undefined ? { selfRef: node.ref } : {}),
70
- })
71
- files.push({ filename: `${node.filename}.ts`, content })
72
- })
73
-
74
- // Emit the runtime contract for validators. ValidationResult is mjst-defined
75
- // (not derived from the input schema), so its content is fixed.
76
- files.push({ filename: 'validation-result.ts', content: VALIDATION_RESULT_CONTENT })
77
-
78
- files.push({ filename: 'index.ts', content: generateIndexBarrel(files) })
79
-
80
- return files
81
- }
@@ -1,134 +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
- /**
8
- * Options for controlling how validator imports are collected.
9
- */
10
- type CollectValidatorImportsOptions = {
11
- /**
12
- * The $ref path of the schema being generated (e.g. `#/$defs/encoding`).
13
- * Prevents a file from importing itself.
14
- */
15
- readonly selfRef?: string | undefined
16
- /**
17
- * The root schema document. URI refs that cannot be resolved within it
18
- * are excluded from the import list (they were never generated as files).
19
- */
20
- readonly rootSchema?: Record<string, unknown> | undefined
21
- /**
22
- * Suffix appended to every type/validator name derived from a `$ref`. Must
23
- * match the suffix used when generating the referenced files. Defaults to `''`.
24
- */
25
- readonly typeSuffix?: string
26
- }
27
-
28
- /**
29
- * Generates an import statement for a single $ref, importing both the type
30
- * and the validator function from the ref's generated file.
31
- */
32
- const buildImport = (ref: string, suffix: string): string => {
33
- const filename = refToFilename(ref)
34
- const typeName = refToName(ref, suffix)
35
- const validatorName = `validate${typeName}`
36
- return `import { type ${typeName}, ${validatorName} } from './${filename}'`
37
- }
38
-
39
- /**
40
- * Resolves the canonical filename for a ref, stripping `-or-reference` suffixes
41
- * so that `#/$defs/parameter-or-reference` maps to `parameter`.
42
- */
43
- const canonicalFilename = (ref: string): string => {
44
- const base = ref.endsWith('-or-reference') ? ref.replace('-or-reference', '') : ref
45
- return refToFilename(base)
46
- }
47
-
48
- /**
49
- * Walks one level of the schema and yields all direct $ref strings that should
50
- * become imports: properties, additionalProperties, items, and union branches.
51
- */
52
- const collectDirectRefs = (schema: JSONSchema): string[] => {
53
- if (typeof schema === 'boolean' || schema === null) return []
54
-
55
- const refs: string[] = []
56
-
57
- if (hasRef(schema)) {
58
- refs.push(schema.$ref)
59
- return refs
60
- }
61
-
62
- const propSchemas =
63
- 'properties' in schema && typeof schema.properties === 'object' && schema.properties !== null
64
- ? Object.values(schema.properties as Record<string, JSONSchema>)
65
- : []
66
-
67
- for (const prop of propSchemas) {
68
- if (hasRef(prop)) refs.push((prop as { $ref: string }).$ref)
69
- if (hasItems(prop) && hasRef(prop.items)) refs.push((prop.items as { $ref: string }).$ref)
70
- if (hasAdditionalProperties(prop) && hasRef(prop.additionalProperties as JSONSchema)) {
71
- refs.push((prop.additionalProperties as { $ref: string }).$ref)
72
- }
73
- }
74
-
75
- if (hasItems(schema) && hasRef(schema.items)) {
76
- refs.push((schema.items as { $ref: string }).$ref)
77
- }
78
-
79
- if (hasAdditionalProperties(schema) && hasRef(schema.additionalProperties as JSONSchema)) {
80
- refs.push((schema.additionalProperties as { $ref: string }).$ref)
81
- }
82
-
83
- for (const branch of [
84
- ...(hasOneOf(schema) ? schema.oneOf : []),
85
- ...(hasAnyOf(schema) ? schema.anyOf : []),
86
- ...(hasAllOf(schema) ? schema.allOf : []),
87
- ]) {
88
- if (hasRef(branch)) refs.push((branch as { $ref: string }).$ref)
89
- }
90
-
91
- return refs
92
- }
93
-
94
- /**
95
- * Collects import statements for all $ref dependencies of a schema.
96
- * Each import brings in both the generated TypeScript type and validator function.
97
- *
98
- * @example
99
- * ```typescript
100
- * const schema = { properties: { contact: { $ref: '#/$defs/contact' } } }
101
- * collectValidatorImports(schema)
102
- * // ["import { type Contact, validateContact } from './contact'"]
103
- * ```
104
- */
105
- export const collectValidatorImports = (schema: JSONSchema, options?: CollectValidatorImportsOptions): string[] => {
106
- const selfFilename = options?.selfRef ? refToFilename(options.selfRef) : null
107
- const rootSchema = options?.rootSchema
108
- const typeSuffix = options?.typeSuffix ?? ''
109
-
110
- const refs = collectDirectRefs(schema)
111
- const seen = new Set<string>()
112
- const imports: string[] = []
113
-
114
- for (const ref of refs) {
115
- const filename = canonicalFilename(ref)
116
-
117
- if (seen.has(filename)) continue
118
- if (selfFilename && filename === selfFilename) continue
119
-
120
- // Skip refs that don't resolve in this schema (external / never generated)
121
- if (rootSchema) {
122
- const resolved = resolveRef(ref, rootSchema)
123
- if (!resolved) continue
124
- }
125
-
126
- seen.add(filename)
127
-
128
- // -or-reference unions import the base type's validator
129
- const importRef = ref.endsWith('-or-reference') ? ref.replace('-or-reference', '') : ref
130
- imports.push(buildImport(importRef, typeSuffix))
131
- }
132
-
133
- return imports
134
- }
@@ -1,79 +0,0 @@
1
- import { generateTypeDefinition } from '@amritk/helpers/generate-type-definition'
2
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
3
-
4
- import { collectValidatorImports } from './collect-validator-imports'
5
- import { generateValidatorFunction } from './generate-validator-function'
6
-
7
- /**
8
- * Options for controlling what gets generated in a validator file.
9
- */
10
- type GenerateValidatorFileOptions = {
11
- /**
12
- * The $ref path of the schema being generated (e.g. `#/$defs/info`).
13
- * Prevents the file from importing itself.
14
- */
15
- readonly selfRef?: string
16
- /**
17
- * The root schema document. Used to filter out unresolvable refs.
18
- */
19
- readonly rootSchema?: Record<string, unknown>
20
- /**
21
- * Suffix appended to every type/validator name derived from a `$ref`.
22
- * Defaults to `''` (no suffix).
23
- */
24
- readonly typeSuffix?: string
25
- }
26
-
27
- /**
28
- * Generates a complete TypeScript validator file from a JSON Schema.
29
- *
30
- * The file contains:
31
- * - Imports for the ValidationResult/ValidationError types
32
- * - Imports for any $ref types and their validator functions
33
- * - The exported TypeScript type definition
34
- * - The exported validator function
35
- *
36
- * @example
37
- * ```typescript
38
- * const schema = {
39
- * type: 'object',
40
- * properties: { title: { type: 'string' } },
41
- * required: ['title'],
42
- * }
43
- * generateValidatorFile(schema, 'Info')
44
- * // import type { ValidationResult, ValidationError } from './validation-result'
45
- * // export type Info = { title: string }
46
- * // export const validateInfo = (input: unknown, _path = ''): ValidationResult => { ... }
47
- * ```
48
- */
49
- export const generateValidatorFile = (
50
- schema: JSONSchema,
51
- typeName: string,
52
- options?: GenerateValidatorFileOptions,
53
- ): string => {
54
- const typeSuffix = options?.typeSuffix ?? ''
55
- const refImports = collectValidatorImports(schema, {
56
- selfRef: options?.selfRef,
57
- rootSchema: options?.rootSchema,
58
- typeSuffix,
59
- })
60
-
61
- const typeDefinition = generateTypeDefinition(schema, typeName, { typeSuffix })
62
- const validatorFunction = generateValidatorFunction(schema, typeName, typeSuffix)
63
-
64
- let result = `import type { ValidationResult, ValidationError } from './validation-result'\n`
65
-
66
- for (const imp of refImports) {
67
- result += imp + '\n'
68
- }
69
-
70
- if (refImports.length > 0) {
71
- result += '\n'
72
- } else {
73
- result += '\n'
74
- }
75
-
76
- result += typeDefinition + '\n\n' + validatorFunction
77
-
78
- return result
79
- }
@@ -1,238 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { generateValidatorFunction } from './generate-validator-function'
4
-
5
- // Eval helper: compiles a generated function string in context of a minimal
6
- // ValidationResult runtime so we can actually run the generated code.
7
- const _evalValidator = (code: string): ((input: unknown, path?: string) => unknown) => {
8
- const _wrapped = `
9
- const ValidationResult = null // type-only
10
- ${code}
11
- `
12
- // eslint-disable-next-line no-new-func
13
- return new Function(`
14
- ${code}
15
- return validate${code.match(/export const validate(\w+)/)?.[1] ?? ''}
16
- `)() as (input: unknown, path?: string) => unknown
17
- }
18
-
19
- describe('generate-validator-function', () => {
20
- it('generates a validator for a required string property', () => {
21
- const schema = {
22
- type: 'object' as const,
23
- properties: { name: { type: 'string' as const } },
24
- required: ['name'],
25
- }
26
- const code = generateValidatorFunction(schema, 'Info')
27
-
28
- expect(code).toContain('export const validateInfo')
29
- expect(code).toContain('"name" in obj')
30
- expect(code).toContain("must have required property 'name'")
31
- expect(code).toContain('typeof obj["name"] !== \'string\'')
32
- expect(code).toContain('must be string')
33
- })
34
-
35
- it('generates a validator for an optional number property', () => {
36
- const schema = {
37
- type: 'object' as const,
38
- properties: { count: { type: 'number' as const } },
39
- }
40
- const code = generateValidatorFunction(schema, 'Stats')
41
-
42
- expect(code).toContain('obj["count"] !== undefined')
43
- expect(code).toContain('typeof obj["count"] !== \'number\'')
44
- expect(code).toContain('must be number')
45
- })
46
-
47
- it('generates a validator for a boolean property', () => {
48
- const schema = {
49
- type: 'object' as const,
50
- properties: { enabled: { type: 'boolean' as const } },
51
- required: ['enabled'],
52
- }
53
- const code = generateValidatorFunction(schema, 'Config')
54
-
55
- expect(code).toContain('typeof obj["enabled"] !== \'boolean\'')
56
- expect(code).toContain('must be boolean')
57
- })
58
-
59
- it('generates required-property check at parent path', () => {
60
- const schema = {
61
- type: 'object' as const,
62
- properties: { title: { type: 'string' as const } },
63
- required: ['title'],
64
- }
65
- const code = generateValidatorFunction(schema, 'Doc')
66
-
67
- // Required errors use _path (parent), not a child path
68
- expect(code).toContain('path: _path')
69
- expect(code).toContain("must have required property 'title'")
70
- })
71
-
72
- it('generates type error at child path', () => {
73
- const schema = {
74
- type: 'object' as const,
75
- properties: { title: { type: 'string' as const } },
76
- required: ['title'],
77
- }
78
- const code = generateValidatorFunction(schema, 'Doc')
79
-
80
- // Type errors use the child path
81
- expect(code).toContain('`${_path}/title`')
82
- })
83
-
84
- it('generates an enum validator', () => {
85
- const schema = {
86
- enum: ['get', 'post', 'put', 'delete'],
87
- }
88
- const code = generateValidatorFunction(schema as Parameters<typeof generateValidatorFunction>[0], 'Method')
89
-
90
- expect(code).toContain('must be one of')
91
- expect(code).toContain('"get"')
92
- expect(code).toContain('"post"')
93
- })
94
-
95
- it('generates a string pattern check', () => {
96
- const schema = {
97
- type: 'object' as const,
98
- properties: { version: { type: 'string' as const, pattern: '^\\d+\\.\\d+' } },
99
- required: ['version'],
100
- }
101
- const code = generateValidatorFunction(schema, 'Info')
102
-
103
- expect(code).toContain('must match pattern')
104
- expect(code).toContain('.test(')
105
- })
106
-
107
- it('generates min/maxLength checks', () => {
108
- const schema = {
109
- type: 'object' as const,
110
- properties: { name: { type: 'string' as const, minLength: 1, maxLength: 100 } },
111
- required: ['name'],
112
- }
113
- const code = generateValidatorFunction(schema, 'Info')
114
-
115
- expect(code).toContain('must have at least 1 characters')
116
- expect(code).toContain('must have at most 100 characters')
117
- })
118
-
119
- it('generates minimum/maximum checks', () => {
120
- const schema = {
121
- type: 'object' as const,
122
- properties: { port: { type: 'number' as const, minimum: 0, maximum: 65535 } },
123
- }
124
- const code = generateValidatorFunction(schema, 'Server')
125
-
126
- expect(code).toContain('>= 0')
127
- expect(code).toContain('<= 65535')
128
- })
129
-
130
- it('generates a $ref property delegation', () => {
131
- const schema = {
132
- type: 'object' as const,
133
- properties: { info: { $ref: '#/$defs/info' } },
134
- required: ['info'],
135
- }
136
- const code = generateValidatorFunction(schema, 'Document')
137
-
138
- expect(code).toContain('validateInfo(')
139
- expect(code).toContain('"info" in obj')
140
- })
141
-
142
- it('generates a scalar string validator', () => {
143
- const schema = { type: 'string' as const }
144
- const code = generateValidatorFunction(schema, 'StringValue')
145
-
146
- expect(code).toContain("typeof input !== 'string'")
147
- expect(code).toContain('must be string')
148
- })
149
-
150
- it('accumulates all constraint errors for a scalar string schema', () => {
151
- const schema = { type: 'string' as const, pattern: '^\\d+$', minLength: 2, maxLength: 4 }
152
- const code = generateValidatorFunction(schema, 'Code')
153
-
154
- // All three constraints push onto a shared errors array instead of returning early
155
- expect(code).toContain('const errors: ValidationError[] = []')
156
- expect(code).toContain("errors.push({ message: 'must match pattern")
157
- expect(code).toContain("errors.push({ message: 'must have at least 2 characters'")
158
- expect(code).toContain("errors.push({ message: 'must have at most 4 characters'")
159
- expect(code).toContain('return errors.length > 0 ? { valid: false, errors } : true')
160
- })
161
-
162
- it('returns true for empty object schemas', () => {
163
- const schema = { type: 'object' as const }
164
- const code = generateValidatorFunction(schema, 'Empty')
165
-
166
- expect(code).toContain('validateEmpty')
167
- expect(code).toContain('must be object')
168
- expect(code).toContain('return errors.length > 0')
169
- })
170
-
171
- it('generates object guard at top of object validator', () => {
172
- const schema = { type: 'object' as const, properties: { x: { type: 'string' as const } } }
173
- const code = generateValidatorFunction(schema, 'Foo')
174
-
175
- expect(code).toContain("typeof input !== 'object'")
176
- expect(code).toContain('Array.isArray(input)')
177
- expect(code).toContain('must be object')
178
- })
179
-
180
- it('generates an instanceof check for a required x-mjst Date property', () => {
181
- const schema = {
182
- type: 'object' as const,
183
- properties: { createdAt: { 'x-mjst': { instanceOf: 'Date' } } },
184
- required: ['createdAt'],
185
- }
186
- const code = generateValidatorFunction(schema, 'Event')
187
-
188
- expect(code).toContain('"createdAt" in obj')
189
- expect(code).toContain('!(obj["createdAt"] instanceof Date)')
190
- expect(code).toContain('must be Date')
191
- })
192
-
193
- it('generates an instanceof check for an optional x-mjst Date property', () => {
194
- const schema = {
195
- type: 'object' as const,
196
- properties: { createdAt: { 'x-mjst': { instanceOf: 'Date' } } },
197
- }
198
- const code = generateValidatorFunction(schema, 'Event')
199
-
200
- expect(code).toContain('obj["createdAt"] !== undefined && !(obj["createdAt"] instanceof Date)')
201
- })
202
-
203
- it('generates an instanceof check for a top-level x-mjst Date schema', () => {
204
- const code = generateValidatorFunction({ 'x-mjst': { instanceOf: 'Date' } }, 'When')
205
-
206
- expect(code).toContain('!(input instanceof Date)')
207
- expect(code).toContain('must be Date')
208
- })
209
-
210
- it('generates a typeof check for a required x-mjst bigint property', () => {
211
- const schema = {
212
- type: 'object' as const,
213
- properties: { balance: { 'x-mjst': { primitive: 'bigint' } } },
214
- required: ['balance'],
215
- }
216
- const code = generateValidatorFunction(schema, 'Account')
217
-
218
- expect(code).toContain('typeof obj["balance"] !== "bigint"')
219
- expect(code).toContain('must be bigint')
220
- })
221
-
222
- it('guards undefined for an optional x-mjst bigint property', () => {
223
- const schema = {
224
- type: 'object' as const,
225
- properties: { balance: { 'x-mjst': { primitive: 'bigint' } } },
226
- }
227
- const code = generateValidatorFunction(schema, 'Account')
228
-
229
- expect(code).toContain('obj["balance"] !== undefined && typeof obj["balance"] !== "bigint"')
230
- })
231
-
232
- it('generates a typeof check for a top-level x-mjst bigint schema', () => {
233
- const code = generateValidatorFunction({ 'x-mjst': { primitive: 'bigint' } }, 'Big')
234
-
235
- expect(code).toContain('typeof input !== "bigint"')
236
- expect(code).toContain('must be bigint')
237
- })
238
- })