Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
23 changes: 22 additions & 1 deletion src/structured/TemplateJson.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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;
Expand All @@ -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]);
}
Expand Down
178 changes: 178 additions & 0 deletions src/structured/__tests__/TemplateJson.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
}
});
});
});

Expand Down
45 changes: 44 additions & 1 deletion src/structured/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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`.
Expand Down