Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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).toBe(BigInt(-9));

const result2 = TemplateJson.gen(['int64', BigInt(0), BigInt(1)]) as bigint;
expect(result2).toBe(BigInt(0));

const result3 = TemplateJson.gen(['int64', BigInt(1), BigInt(5)]) as bigint;
expect(result3).toBe(BigInt(4));
});

test('handles edge cases', () => {
resetMathRandom();
const result1 = TemplateJson.gen(['int64', BigInt(0), BigInt(0)]) as bigint;
expect(result1).toBe(BigInt(0));

const result2 = TemplateJson.gen(['int64', BigInt(-1), BigInt(-1)]) as bigint;
expect(result2).toBe(BigInt(-1));

const result3 = TemplateJson.gen(['int64', BigInt('1000000000000'), BigInt('1000000000000')]) as bigint;
expect(result3).toBe(BigInt('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
Loading