UNPKG

typescript-runtime-schemas

Version:

A TypeScript schema generation tool that extracts Zod schemas from TypeScript source files with runtime validation support. Generate validation schemas directly from your existing TypeScript types with support for computed types and constraint-based valid

701 lines (681 loc) 25.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const type_parser_1 = require("./type-parser"); describe('TypeParser', () => { describe('Basic Type Parsing', () => { it('should parse simple string type with constraints', () => { const sourceCode = ` type Constraint<K extends any> = {}; type Min<N extends number> = Constraint<N>; type Max<N extends number> = Constraint<N>; type SimpleType = { id: string & Min<10> & Max<80>; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('SimpleType'); expect(result).toEqual({ id: { type: 'string', required: true, constraints: { min: 10, max: 80 }, }, }); }); it('should parse number type with constraints', () => { const sourceCode = ` type Constraint<K extends any> = {}; type Min<N extends number> = Constraint<N>; type Max<N extends number> = Constraint<N>; type NumberType = { age: number & Min<18> & Max<120>; score?: number & Min<0> & Max<100>; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('NumberType'); expect(result).toEqual({ age: { type: 'number', required: true, constraints: { min: 18, max: 120 }, }, score: { type: 'number', required: false, constraints: { min: 0, max: 100 }, }, }); }); it('should parse boolean type', () => { const sourceCode = ` type BooleanType = { isActive: boolean; isOptional?: boolean; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('BooleanType'); expect(result).toEqual({ isActive: { type: 'boolean', required: true, constraints: {}, }, isOptional: { type: 'boolean', required: false, constraints: {}, }, }); }); }); describe('String Constraints', () => { it('should parse string length constraints', () => { const sourceCode = ` type Constraint<K extends any> = {}; type MinLength<N extends number> = Constraint<N>; type MaxLength<N extends number> = Constraint<N>; type StringConstraints = { username: string & MinLength<3> & MaxLength<20>; description?: string & MaxLength<500>; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('StringConstraints'); expect(result).toEqual({ username: { type: 'string', required: true, constraints: { minLength: 3, maxLength: 20 }, }, description: { type: 'string', required: false, constraints: { maxLength: 500 }, }, }); }); it('should parse email constraint', () => { const sourceCode = ` type Constraint<K extends any> = {}; type Email = Constraint<string>; type EmailType = { email: string & Email; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('EmailType'); expect(result).toEqual({ email: { type: 'string', required: true, constraints: { email: true }, }, }); }); it('should parse multiple string constraints', () => { const sourceCode = ` type Constraint<K extends any> = {}; type Email = Constraint<string>; type UUID = Constraint<string>; type URL = Constraint<string>; type MultipleConstraints = { email: string & Email; id: string & UUID; website?: string & URL; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('MultipleConstraints'); expect(result).toEqual({ email: { type: 'string', required: true, constraints: { email: true }, }, id: { type: 'string', required: true, constraints: { uuid: true }, }, website: { type: 'string', required: false, constraints: { url: true }, }, }); }); it('should parse regex constraint with pattern', () => { const sourceCode = ` type Constraint<K extends any> = {}; type Regex<P extends string> = Constraint<P>; type RegexConstraints = { phoneNumber: string & Regex<"^\\\\+?[1-9]\\\\d{1,14}$">; zipCode: string & Regex<"^\\\\d{5}(-\\\\d{4})?$">; username?: string & Regex<"^[a-zA-Z0-9_]{3,20}$">; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('RegexConstraints'); expect(result).toEqual({ phoneNumber: { type: 'string', required: true, constraints: { regex: '^\\+?[1-9]\\d{1,14}$' }, }, zipCode: { type: 'string', required: true, constraints: { regex: '^\\d{5}(-\\d{4})?$' }, }, username: { type: 'string', required: false, constraints: { regex: '^[a-zA-Z0-9_]{3,20}$' }, }, }); }); }); describe('Array Type Parsing', () => { it('should parse simple arrays', () => { const sourceCode = ` type ArrayType = { tags: string[]; numbers?: number[]; flags: boolean[]; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('ArrayType'); expect(result).toEqual({ tags: { type: 'array', required: true, constraints: {}, arrayElementType: { type: 'string', required: true, constraints: {}, }, }, numbers: { type: 'array', required: false, constraints: {}, arrayElementType: { type: 'number', required: true, constraints: {}, }, }, flags: { type: 'array', required: true, constraints: {}, arrayElementType: { type: 'boolean', required: true, constraints: {}, }, }, }); }); it('should parse arrays with length constraints', () => { const sourceCode = ` type Constraint<K extends any> = {}; type MinLength<N extends number> = Constraint<N>; type MaxLength<N extends number> = Constraint<N>; type ArrayConstraints = { tags: string[] & MinLength<1> & MaxLength<10>; scores: number[] & MinLength<3> & MaxLength<100>; categories?: string[] & MinLength<2>; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('ArrayConstraints'); expect(result).toEqual({ tags: { type: 'array', required: true, constraints: { minLength: 1, maxLength: 10 }, arrayElementType: { type: 'string', required: true, constraints: {}, }, }, scores: { type: 'array', required: true, constraints: { minLength: 3, maxLength: 100 }, arrayElementType: { type: 'number', required: true, constraints: {}, }, }, categories: { type: 'array', required: false, constraints: { minLength: 2 }, arrayElementType: { type: 'string', required: true, constraints: {}, }, }, }); }); }); describe('Nested Object Parsing', () => { it('should parse nested objects', () => { const sourceCode = ` type Constraint<K extends any> = {}; type MinLength<N extends number> = Constraint<N>; type MaxLength<N extends number> = Constraint<N>; type Email = Constraint<string>; type Contact = { email: string & Email; phone?: string & MinLength<10> & MaxLength<15>; }; type NestedType = { name: string & MinLength<2> & MaxLength<50>; contact: Contact; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('NestedType'); expect(result).toEqual({ name: { type: 'string', required: true, constraints: { minLength: 2, maxLength: 50 }, }, contact: { type: 'object', required: true, constraints: {}, properties: { email: { type: 'string', required: true, constraints: { email: true }, }, phone: { type: 'string', required: false, constraints: { minLength: 10, maxLength: 15 }, }, }, }, }); }); it('should parse inline nested objects', () => { const sourceCode = ` type Constraint<K extends any> = {}; type MinLength<N extends number> = Constraint<N>; type Max<N extends number> = Constraint<N>; type InlineNested = { settings: { theme: string & MinLength<3>; maxUsers: number & Max<1000>; enabled?: boolean; }; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('InlineNested'); expect(result).toEqual({ settings: { type: 'object', required: true, constraints: {}, properties: { theme: { type: 'string', required: true, constraints: { minLength: 3 }, }, maxUsers: { type: 'number', required: true, constraints: { max: 1000 }, }, enabled: { type: 'boolean', required: false, constraints: {}, }, }, }, }); }); it('should parse deeply nested objects', () => { const sourceCode = ` type Constraint<K extends any> = {}; type Min<N extends number> = Constraint<N>; type Max<N extends number> = Constraint<N>; type DeeplyNested = { level1: { level2: { level3: { value: number & Min<1> & Max<100>; }; }; }; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('DeeplyNested'); expect(result).toEqual({ level1: { type: 'object', required: true, constraints: {}, properties: { level2: { type: 'object', required: true, constraints: {}, properties: { level3: { type: 'object', required: true, constraints: {}, properties: { value: { type: 'number', required: true, constraints: { min: 1, max: 100 }, }, }, }, }, }, }, }, }); }); }); describe('Arrays of Objects', () => { it('should parse arrays of custom objects', () => { const sourceCode = ` type Constraint<K extends any> = {}; type MinLength<N extends number> = Constraint<N>; type MaxLength<N extends number> = Constraint<N>; type Min<N extends number> = Constraint<N>; type Max<N extends number> = Constraint<N>; type Person = { name: string & MinLength<2> & MaxLength<50>; age: number & Min<0> & Max<150>; }; type ArrayOfObjects = { people: Person[] & MinLength<1> & MaxLength<100>; optionalPeople?: Person[]; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('ArrayOfObjects'); expect(result).toEqual({ people: { type: 'array', required: true, constraints: { minLength: 1, maxLength: 100 }, arrayElementType: { type: 'object', required: true, constraints: {}, properties: { name: { type: 'string', required: true, constraints: { minLength: 2, maxLength: 50 }, }, age: { type: 'number', required: true, constraints: { min: 0, max: 150 }, }, }, }, }, optionalPeople: { type: 'array', required: false, constraints: {}, arrayElementType: { type: 'object', required: true, constraints: {}, properties: { name: { type: 'string', required: true, constraints: { minLength: 2, maxLength: 50 }, }, age: { type: 'number', required: true, constraints: { min: 0, max: 150 }, }, }, }, }, }); }); it('should parse arrays of inline objects', () => { const sourceCode = ` type Constraint<K extends any> = {}; type MinLength<N extends number> = Constraint<N>; type Min<N extends number> = Constraint<N>; type Max<N extends number> = Constraint<N>; type ArrayOfInlineObjects = { permissions: { role: string & MinLength<3>; level: number & Min<1> & Max<10>; }[] & MinLength<1>; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('ArrayOfInlineObjects'); expect(result).toEqual({ permissions: { type: 'array', required: true, constraints: { minLength: 1 }, arrayElementType: { type: 'object', required: true, constraints: {}, properties: { role: { type: 'string', required: true, constraints: { minLength: 3 }, }, level: { type: 'number', required: true, constraints: { min: 1, max: 10 }, }, }, }, }, }); }); }); describe('Complex Mixed Types', () => { it('should parse complex type with all features', () => { const sourceCode = ` type Constraint<K extends any> = {}; type Min<N extends number> = Constraint<N>; type Max<N extends number> = Constraint<N>; type MinLength<N extends number> = Constraint<N>; type MaxLength<N extends number> = Constraint<N>; type Email = Constraint<string>; type Address = { street: string & MinLength<5> & MaxLength<100>; city: string & MinLength<2> & MaxLength<50>; zipCode: string & MinLength<5> & MaxLength<10>; country?: string & MaxLength<50>; }; type Contact = { email: string & Email; phone?: string & MinLength<10> & MaxLength<15>; }; type Person = { name: string & MinLength<2> & MaxLength<50>; age: number & Min<0> & Max<150>; contact: Contact; address?: Address; }; type ComplexType = { owner: Person; employees: Person[] & MinLength<1> & MaxLength<100>; contractors?: Person[] & MaxLength<50>; settings: { theme: string & MinLength<3> & MaxLength<20>; notifications: boolean; limits: { maxUsers: number & Min<1> & Max<1000>; maxStorage: number & Min<100>; }; }; permissions: { role: string & MinLength<3> & MaxLength<20>; level: number & Min<1> & Max<10>; }[] & MinLength<1>; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('ComplexType'); // Test key parts of the complex structure expect(result.owner.type).toBe('object'); expect(result.owner.properties?.name.constraints).toEqual({ minLength: 2, maxLength: 50, }); expect(result.owner.properties?.contact.properties?.email.constraints).toEqual({ email: true }); expect(result.employees.type).toBe('array'); expect(result.employees.constraints).toEqual({ minLength: 1, maxLength: 100, }); expect(result.employees.arrayElementType?.type).toBe('object'); expect(result.settings.type).toBe('object'); expect(result.settings.properties?.limits.properties?.maxUsers.constraints).toEqual({ min: 1, max: 1000 }); expect(result.permissions.type).toBe('array'); expect(result.permissions.arrayElementType?.properties?.role.constraints).toEqual({ minLength: 3, maxLength: 20 }); }); }); describe('Edge Cases', () => { it('should handle type not found', () => { const sourceCode = ` type SimpleType = { id: string; }; `; const parser = new type_parser_1.TypeParser(sourceCode); expect(() => { parser.parseType('NonExistentType'); }).toThrow('Type NonExistentType not found'); }); it('should handle empty type', () => { const sourceCode = ` type EmptyType = {}; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('EmptyType'); expect(result).toEqual({}); }); it('should handle type with no constraints', () => { const sourceCode = ` type NoConstraints = { name: string; age: number; active: boolean; }; `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('NoConstraints'); expect(result).toEqual({ name: { type: 'string', required: true, constraints: {}, }, age: { type: 'number', required: true, constraints: {}, }, active: { type: 'boolean', required: true, constraints: {}, }, }); }); }); describe('Convenience Function', () => { it('should work with parseTypeFromSource function', () => { const sourceCode = ` type Constraint<K extends any> = {}; type Min<N extends number> = Constraint<N>; type Max<N extends number> = Constraint<N>; type TestType = { id: string & Min<10> & Max<80>; age?: number & Min<1>; }; `; const result = (0, type_parser_1.parseTypeFromSource)('TestType', sourceCode); expect(result).toEqual({ id: { type: 'string', required: true, constraints: { min: 10, max: 80 }, }, age: { type: 'number', required: false, constraints: { min: 1 }, }, }); }); }); describe('Interface Support', () => { it('should parse interface declarations', () => { const sourceCode = ` type Constraint<K extends any> = {}; type MinLength<N extends number> = Constraint<N>; type Email = Constraint<string>; interface UserInterface { username: string & MinLength<3>; email: string & Email; age?: number; } `; const parser = new type_parser_1.TypeParser(sourceCode); const result = parser.parseType('UserInterface'); expect(result).toEqual({ username: { type: 'string', required: true, constraints: { minLength: 3 }, }, email: { type: 'string', required: true, constraints: { email: true }, }, age: { type: 'number', required: false, constraints: {}, }, }); }); }); }); //# sourceMappingURL=type-parser.test.js.map