diff --git a/src/number.ts b/src/number.ts index c0ee995..7f0f95b 100644 --- a/src/number.ts +++ b/src/number.ts @@ -3,3 +3,12 @@ export const int = (min: number, max: number): number => { int = Math.max(min, Math.min(max, int)); return int; }; + +export const int64 = (min: bigint, max: bigint): bigint => { + const range = max - min; + const randomFloat = Math.random(); + const randomBigInt = BigInt(Math.floor(Number(range) * randomFloat)); + let result = min + randomBigInt; + result = result < min ? min : result > max ? max : result; + return result; +}; diff --git a/src/structured/TemplateJson.ts b/src/structured/TemplateJson.ts index d2dd962..a38983a 100644 --- a/src/structured/TemplateJson.ts +++ b/src/structured/TemplateJson.ts @@ -1,12 +1,14 @@ -import {int} from '../number'; +import {int, int64} from '../number'; import {randomString} from '../string'; import {clone} from '../util'; import * as templates from './templates'; import type { ArrayTemplate, + BinTemplate, BooleanTemplate, FloatTemplate, IntegerTemplate, + Int64Template, LiteralTemplate, MapTemplate, NumberTemplate, @@ -66,10 +68,14 @@ export class TemplateJson { return this.generateNumber(template as NumberTemplate); case 'int': return this.generateInteger(template as IntegerTemplate); + case 'int64': + return this.generateInt64(template as Int64Template); case 'float': return this.generateFloat(template as FloatTemplate); case 'bool': return this.generateBoolean(template as BooleanTemplate); + case 'bin': + return this.generateBin(template as BinTemplate); case 'nil': return null; case 'lit': @@ -141,6 +147,11 @@ export class TemplateJson { return int(min, max); } + protected generateInt64(template: Int64Template): bigint { + const [, min = BigInt('-9223372036854775808'), max = BigInt('9223372036854775807')] = template; + return int64(min, max); + } + protected generateFloat(template: FloatTemplate): number { const [, min = -Number.MAX_VALUE, max = Number.MAX_VALUE] = template; let float = Math.random() * (max - min) + min; @@ -153,6 +164,16 @@ export class TemplateJson { return value !== undefined ? value : Math.random() < 0.5; } + protected generateBin(template: BinTemplate): Uint8Array { + const [, min = 0, max = 5, omin = 0, omax = 255] = template; + const length = this.minmax(min, max); + const result = new Uint8Array(length); + for (let i = 0; i < length; i++) { + result[i] = int(omin, omax); + } + return result; + } + protected generateLiteral(template: LiteralTemplate): unknown { return clone(template[1]); } diff --git a/src/structured/__tests__/TemplateJson.spec.ts b/src/structured/__tests__/TemplateJson.spec.ts index 5907f54..af8e17e 100644 --- a/src/structured/__tests__/TemplateJson.spec.ts +++ b/src/structured/__tests__/TemplateJson.spec.ts @@ -48,6 +48,78 @@ describe('TemplateJson', () => { }); }); + describe('int64', () => { + test('uses default int64 schema, if not provided', () => { + resetMathRandom(); + const result = TemplateJson.gen('int64') as bigint; + expect(typeof result).toBe('bigint'); + expect(result >= BigInt('-9223372036854775808')).toBe(true); + expect(result <= BigInt('9223372036854775807')).toBe(true); + }); + + test('can specify int64 range', () => { + resetMathRandom(); + const result1 = TemplateJson.gen(['int64', BigInt(-10), BigInt(10)]) as bigint; + expect(result1.toString()).toBe('-9'); + + const result2 = TemplateJson.gen(['int64', BigInt(0), BigInt(1)]) as bigint; + expect(result2.toString()).toBe('0'); + + const result3 = TemplateJson.gen(['int64', BigInt(1), BigInt(5)]) as bigint; + expect(result3.toString()).toBe('3'); + }); + + test('handles edge cases', () => { + resetMathRandom(); + const result1 = TemplateJson.gen(['int64', BigInt(0), BigInt(0)]) as bigint; + expect(result1.toString()).toBe('0'); + + const result2 = TemplateJson.gen(['int64', BigInt(-1), BigInt(-1)]) as bigint; + expect(result2.toString()).toBe('-1'); + + const result3 = TemplateJson.gen(['int64', BigInt('1000000000000'), BigInt('1000000000000')]) as bigint; + expect(result3.toString()).toBe('1000000000000'); + }); + + test('handles very large ranges', () => { + resetMathRandom(); + const result = TemplateJson.gen([ + 'int64', + BigInt('-9223372036854775808'), + BigInt('9223372036854775807'), + ]) as bigint; + expect(typeof result).toBe('bigint'); + expect(result >= BigInt('-9223372036854775808')).toBe(true); + expect(result <= BigInt('9223372036854775807')).toBe(true); + }); + + test('can be used in complex structures', () => { + resetMathRandom(); + const template: any = [ + 'obj', + [ + ['id', 'int64'], + ['timestamp', ['int64', BigInt('1000000000000'), BigInt('9999999999999')]], + ], + ]; + const result = TemplateJson.gen(template) as any; + expect(typeof result).toBe('object'); + expect(typeof result.id).toBe('bigint'); + expect(typeof result.timestamp).toBe('bigint'); + expect(result.timestamp >= BigInt('1000000000000')).toBe(true); + expect(result.timestamp <= BigInt('9999999999999')).toBe(true); + }); + + test('works with or templates', () => { + resetMathRandom(); + const result = TemplateJson.gen(['or', 'int', 'int64', 'str']); + const isBigInt = typeof result === 'bigint'; + const isNumber = typeof result === 'number'; + const isString = typeof result === 'string'; + expect(isBigInt || isNumber || isString).toBe(true); + }); + }); + describe('num', () => { test('generates random number, without range', () => { resetMathRandom(); @@ -112,6 +184,66 @@ describe('TemplateJson', () => { }); }); + describe('bin', () => { + test('uses default binary schema, if not provided', () => { + resetMathRandom(); + const bin = TemplateJson.gen('bin'); + expect(bin instanceof Uint8Array).toBe(true); + expect((bin as Uint8Array).length).toBeGreaterThanOrEqual(0); + expect((bin as Uint8Array).length).toBeLessThanOrEqual(5); + }); + + test('can specify length range', () => { + resetMathRandom(); + const bin = TemplateJson.gen(['bin', 2, 4]) as Uint8Array; + expect(bin instanceof Uint8Array).toBe(true); + expect(bin.length).toBeGreaterThanOrEqual(2); + expect(bin.length).toBeLessThanOrEqual(4); + }); + + test('can specify octet value range', () => { + resetMathRandom(); + const bin = TemplateJson.gen(['bin', 5, 5, 100, 150]) as Uint8Array; + expect(bin instanceof Uint8Array).toBe(true); + expect(bin.length).toBe(5); + for (let i = 0; i < bin.length; i++) { + expect(bin[i]).toBeGreaterThanOrEqual(100); + expect(bin[i]).toBeLessThanOrEqual(150); + } + }); + + test('handles edge cases', () => { + // Empty array + const empty = TemplateJson.gen(['bin', 0, 0]) as Uint8Array; + expect(empty instanceof Uint8Array).toBe(true); + expect(empty.length).toBe(0); + + // Single byte with fixed value range + resetMathRandom(); + const single = TemplateJson.gen(['bin', 1, 1, 42, 42]) as Uint8Array; + expect(single instanceof Uint8Array).toBe(true); + expect(single.length).toBe(1); + expect(single[0]).toBe(42); + }); + + test('uses default octet range when not specified', () => { + resetMathRandom(); + const bin = TemplateJson.gen(['bin', 3, 3]) as Uint8Array; + expect(bin instanceof Uint8Array).toBe(true); + expect(bin.length).toBe(3); + for (let i = 0; i < bin.length; i++) { + expect(bin[i]).toBeGreaterThanOrEqual(0); + expect(bin[i]).toBeLessThanOrEqual(255); + } + }); + + test('respects maxNodes limit', () => { + const bin = TemplateJson.gen(['bin', 10, 20], {maxNodes: 5}) as Uint8Array; + expect(bin instanceof Uint8Array).toBe(true); + expect(bin.length).toBeLessThanOrEqual(10); + }); + }); + describe('nil', () => { test('always returns null', () => { expect(TemplateJson.gen('nil')).toBe(null); @@ -375,6 +507,16 @@ describe('TemplateJson', () => { const result = TemplateJson.gen(['or', ['lit', 'only']]); expect(result).toBe('only'); }); + + test('works with bin templates', () => { + resetMathRandom(); + const result = TemplateJson.gen(['or', 'str', 'int', ['bin', 2, 2]]); + // Result should be one of the template types + const isString = typeof result === 'string'; + const isNumber = typeof result === 'number'; + const isBin = result instanceof Uint8Array; + expect(isString || isNumber || isBin).toBe(true); + }); }); describe('maxNodeCount', () => { @@ -449,6 +591,42 @@ describe('TemplateJson', () => { expect(typeof result).toBe('number'); expect(Number.isInteger(result)).toBe(true); }); + + test('handles bin templates in complex structures', () => { + resetMathRandom(); + const template: any = [ + 'obj', + [ + ['name', 'str'], + ['data', ['bin', 3, 3]], + [ + 'metadata', + [ + 'obj', + [ + ['hash', ['bin', 32, 32]], + ['signature', ['bin', 64, 64, 0, 127]], + ], + ], + ], + ], + ]; + const result = TemplateJson.gen(template) as any; + expect(typeof result).toBe('object'); + expect(typeof result.name).toBe('string'); + expect(result.data instanceof Uint8Array).toBe(true); + expect(result.data.length).toBe(3); + expect(typeof result.metadata).toBe('object'); + expect(result.metadata.hash instanceof Uint8Array).toBe(true); + expect(result.metadata.hash.length).toBe(32); + expect(result.metadata.signature instanceof Uint8Array).toBe(true); + expect(result.metadata.signature.length).toBe(64); + // Check signature values are in the specified range + for (let i = 0; i < result.metadata.signature.length; i++) { + expect(result.metadata.signature[i]).toBeGreaterThanOrEqual(0); + expect(result.metadata.signature[i]).toBeLessThanOrEqual(127); + } + }); }); }); diff --git a/src/structured/types.ts b/src/structured/types.ts index fdfe959..2e58705 100644 --- a/src/structured/types.ts +++ b/src/structured/types.ts @@ -9,16 +9,29 @@ export type TemplateNode = | LiteralTemplate | NumberTemplate | IntegerTemplate + | Int64Template | FloatTemplate | StringTemplate | BooleanTemplate + | BinTemplate | NullTemplate | ArrayTemplate | ObjectTemplate | MapTemplate | OrTemplate; -export type TemplateShorthand = 'num' | 'int' | 'float' | 'str' | 'bool' | 'nil' | 'arr' | 'obj' | 'map'; +export type TemplateShorthand = + | 'num' + | 'int' + | 'int64' + | 'float' + | 'str' + | 'bool' + | 'bin' + | 'nil' + | 'arr' + | 'obj' + | 'map'; /** * Recursive reference allows for recursive template construction, for example: @@ -59,6 +72,12 @@ export type NumberTemplate = [type: 'num', min?: number, max?: number]; */ export type IntegerTemplate = [type: 'int', min?: number, max?: number]; +/** + * 64-bit integer template. Generates a random bigint within the specified range. + * If no range is specified, it defaults to a reasonable range for 64-bit integers. + */ +export type Int64Template = [type: 'int64', min?: bigint, max?: bigint]; + /** * Float template. Generates a random floating-point number within the specified * range. If no range is specified, it defaults to the full range of JavaScript @@ -80,6 +99,30 @@ export type StringTemplate = [type: 'str', token?: Token]; */ export type BooleanTemplate = [type: 'bool', value?: boolean]; +/** + * Binary template. Generates a random Uint8Array. The template allows + * specifying the length of binary data and the range of values in each octet. + */ +export type BinTemplate = [ + type: 'bin', + /** + * The minimum length of binary data. Defaults to 0. + */ + min?: number, + /** + * The maximum length of binary data. Defaults to 5. + */ + max?: number, + /** + * The minimum octet value. Defaults to 0. + */ + omin?: number, + /** + * The maximum octet value. Defaults to 255. + */ + omax?: number, +]; + /** * Null template. Generates a `null` value. If a specific value is provided, it * will always return that value; otherwise, it returns `null`.