Skip to content

Commit 42adcc5

Browse files
authored
Merge pull request #7 from jsonjoy-com/copilot/fix-6
feat: implement bin and int64 templates for Uint8Array and bigint generation in TemplateJson
2 parents d5c3d4b + 628f6fd commit 42adcc5

File tree

4 files changed

+253
-2
lines changed

4 files changed

+253
-2
lines changed

src/number.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,12 @@ export const int = (min: number, max: number): number => {
33
int = Math.max(min, Math.min(max, int));
44
return int;
55
};
6+
7+
export const int64 = (min: bigint, max: bigint): bigint => {
8+
const range = max - min;
9+
const randomFloat = Math.random();
10+
const randomBigInt = BigInt(Math.floor(Number(range) * randomFloat));
11+
let result = min + randomBigInt;
12+
result = result < min ? min : result > max ? max : result;
13+
return result;
14+
};

src/structured/TemplateJson.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import {int} from '../number';
1+
import {int, int64} from '../number';
22
import {randomString} from '../string';
33
import {clone} from '../util';
44
import * as templates from './templates';
55
import type {
66
ArrayTemplate,
7+
BinTemplate,
78
BooleanTemplate,
89
FloatTemplate,
910
IntegerTemplate,
11+
Int64Template,
1012
LiteralTemplate,
1113
MapTemplate,
1214
NumberTemplate,
@@ -66,10 +68,14 @@ export class TemplateJson {
6668
return this.generateNumber(template as NumberTemplate);
6769
case 'int':
6870
return this.generateInteger(template as IntegerTemplate);
71+
case 'int64':
72+
return this.generateInt64(template as Int64Template);
6973
case 'float':
7074
return this.generateFloat(template as FloatTemplate);
7175
case 'bool':
7276
return this.generateBoolean(template as BooleanTemplate);
77+
case 'bin':
78+
return this.generateBin(template as BinTemplate);
7379
case 'nil':
7480
return null;
7581
case 'lit':
@@ -141,6 +147,11 @@ export class TemplateJson {
141147
return int(min, max);
142148
}
143149

150+
protected generateInt64(template: Int64Template): bigint {
151+
const [, min = BigInt('-9223372036854775808'), max = BigInt('9223372036854775807')] = template;
152+
return int64(min, max);
153+
}
154+
144155
protected generateFloat(template: FloatTemplate): number {
145156
const [, min = -Number.MAX_VALUE, max = Number.MAX_VALUE] = template;
146157
let float = Math.random() * (max - min) + min;
@@ -153,6 +164,16 @@ export class TemplateJson {
153164
return value !== undefined ? value : Math.random() < 0.5;
154165
}
155166

167+
protected generateBin(template: BinTemplate): Uint8Array {
168+
const [, min = 0, max = 5, omin = 0, omax = 255] = template;
169+
const length = this.minmax(min, max);
170+
const result = new Uint8Array(length);
171+
for (let i = 0; i < length; i++) {
172+
result[i] = int(omin, omax);
173+
}
174+
return result;
175+
}
176+
156177
protected generateLiteral(template: LiteralTemplate): unknown {
157178
return clone(template[1]);
158179
}

src/structured/__tests__/TemplateJson.spec.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,78 @@ describe('TemplateJson', () => {
4848
});
4949
});
5050

51+
describe('int64', () => {
52+
test('uses default int64 schema, if not provided', () => {
53+
resetMathRandom();
54+
const result = TemplateJson.gen('int64') as bigint;
55+
expect(typeof result).toBe('bigint');
56+
expect(result >= BigInt('-9223372036854775808')).toBe(true);
57+
expect(result <= BigInt('9223372036854775807')).toBe(true);
58+
});
59+
60+
test('can specify int64 range', () => {
61+
resetMathRandom();
62+
const result1 = TemplateJson.gen(['int64', BigInt(-10), BigInt(10)]) as bigint;
63+
expect(result1.toString()).toBe('-9');
64+
65+
const result2 = TemplateJson.gen(['int64', BigInt(0), BigInt(1)]) as bigint;
66+
expect(result2.toString()).toBe('0');
67+
68+
const result3 = TemplateJson.gen(['int64', BigInt(1), BigInt(5)]) as bigint;
69+
expect(result3.toString()).toBe('3');
70+
});
71+
72+
test('handles edge cases', () => {
73+
resetMathRandom();
74+
const result1 = TemplateJson.gen(['int64', BigInt(0), BigInt(0)]) as bigint;
75+
expect(result1.toString()).toBe('0');
76+
77+
const result2 = TemplateJson.gen(['int64', BigInt(-1), BigInt(-1)]) as bigint;
78+
expect(result2.toString()).toBe('-1');
79+
80+
const result3 = TemplateJson.gen(['int64', BigInt('1000000000000'), BigInt('1000000000000')]) as bigint;
81+
expect(result3.toString()).toBe('1000000000000');
82+
});
83+
84+
test('handles very large ranges', () => {
85+
resetMathRandom();
86+
const result = TemplateJson.gen([
87+
'int64',
88+
BigInt('-9223372036854775808'),
89+
BigInt('9223372036854775807'),
90+
]) as bigint;
91+
expect(typeof result).toBe('bigint');
92+
expect(result >= BigInt('-9223372036854775808')).toBe(true);
93+
expect(result <= BigInt('9223372036854775807')).toBe(true);
94+
});
95+
96+
test('can be used in complex structures', () => {
97+
resetMathRandom();
98+
const template: any = [
99+
'obj',
100+
[
101+
['id', 'int64'],
102+
['timestamp', ['int64', BigInt('1000000000000'), BigInt('9999999999999')]],
103+
],
104+
];
105+
const result = TemplateJson.gen(template) as any;
106+
expect(typeof result).toBe('object');
107+
expect(typeof result.id).toBe('bigint');
108+
expect(typeof result.timestamp).toBe('bigint');
109+
expect(result.timestamp >= BigInt('1000000000000')).toBe(true);
110+
expect(result.timestamp <= BigInt('9999999999999')).toBe(true);
111+
});
112+
113+
test('works with or templates', () => {
114+
resetMathRandom();
115+
const result = TemplateJson.gen(['or', 'int', 'int64', 'str']);
116+
const isBigInt = typeof result === 'bigint';
117+
const isNumber = typeof result === 'number';
118+
const isString = typeof result === 'string';
119+
expect(isBigInt || isNumber || isString).toBe(true);
120+
});
121+
});
122+
51123
describe('num', () => {
52124
test('generates random number, without range', () => {
53125
resetMathRandom();
@@ -112,6 +184,66 @@ describe('TemplateJson', () => {
112184
});
113185
});
114186

187+
describe('bin', () => {
188+
test('uses default binary schema, if not provided', () => {
189+
resetMathRandom();
190+
const bin = TemplateJson.gen('bin');
191+
expect(bin instanceof Uint8Array).toBe(true);
192+
expect((bin as Uint8Array).length).toBeGreaterThanOrEqual(0);
193+
expect((bin as Uint8Array).length).toBeLessThanOrEqual(5);
194+
});
195+
196+
test('can specify length range', () => {
197+
resetMathRandom();
198+
const bin = TemplateJson.gen(['bin', 2, 4]) as Uint8Array;
199+
expect(bin instanceof Uint8Array).toBe(true);
200+
expect(bin.length).toBeGreaterThanOrEqual(2);
201+
expect(bin.length).toBeLessThanOrEqual(4);
202+
});
203+
204+
test('can specify octet value range', () => {
205+
resetMathRandom();
206+
const bin = TemplateJson.gen(['bin', 5, 5, 100, 150]) as Uint8Array;
207+
expect(bin instanceof Uint8Array).toBe(true);
208+
expect(bin.length).toBe(5);
209+
for (let i = 0; i < bin.length; i++) {
210+
expect(bin[i]).toBeGreaterThanOrEqual(100);
211+
expect(bin[i]).toBeLessThanOrEqual(150);
212+
}
213+
});
214+
215+
test('handles edge cases', () => {
216+
// Empty array
217+
const empty = TemplateJson.gen(['bin', 0, 0]) as Uint8Array;
218+
expect(empty instanceof Uint8Array).toBe(true);
219+
expect(empty.length).toBe(0);
220+
221+
// Single byte with fixed value range
222+
resetMathRandom();
223+
const single = TemplateJson.gen(['bin', 1, 1, 42, 42]) as Uint8Array;
224+
expect(single instanceof Uint8Array).toBe(true);
225+
expect(single.length).toBe(1);
226+
expect(single[0]).toBe(42);
227+
});
228+
229+
test('uses default octet range when not specified', () => {
230+
resetMathRandom();
231+
const bin = TemplateJson.gen(['bin', 3, 3]) as Uint8Array;
232+
expect(bin instanceof Uint8Array).toBe(true);
233+
expect(bin.length).toBe(3);
234+
for (let i = 0; i < bin.length; i++) {
235+
expect(bin[i]).toBeGreaterThanOrEqual(0);
236+
expect(bin[i]).toBeLessThanOrEqual(255);
237+
}
238+
});
239+
240+
test('respects maxNodes limit', () => {
241+
const bin = TemplateJson.gen(['bin', 10, 20], {maxNodes: 5}) as Uint8Array;
242+
expect(bin instanceof Uint8Array).toBe(true);
243+
expect(bin.length).toBeLessThanOrEqual(10);
244+
});
245+
});
246+
115247
describe('nil', () => {
116248
test('always returns null', () => {
117249
expect(TemplateJson.gen('nil')).toBe(null);
@@ -375,6 +507,16 @@ describe('TemplateJson', () => {
375507
const result = TemplateJson.gen(['or', ['lit', 'only']]);
376508
expect(result).toBe('only');
377509
});
510+
511+
test('works with bin templates', () => {
512+
resetMathRandom();
513+
const result = TemplateJson.gen(['or', 'str', 'int', ['bin', 2, 2]]);
514+
// Result should be one of the template types
515+
const isString = typeof result === 'string';
516+
const isNumber = typeof result === 'number';
517+
const isBin = result instanceof Uint8Array;
518+
expect(isString || isNumber || isBin).toBe(true);
519+
});
378520
});
379521

380522
describe('maxNodeCount', () => {
@@ -449,6 +591,42 @@ describe('TemplateJson', () => {
449591
expect(typeof result).toBe('number');
450592
expect(Number.isInteger(result)).toBe(true);
451593
});
594+
595+
test('handles bin templates in complex structures', () => {
596+
resetMathRandom();
597+
const template: any = [
598+
'obj',
599+
[
600+
['name', 'str'],
601+
['data', ['bin', 3, 3]],
602+
[
603+
'metadata',
604+
[
605+
'obj',
606+
[
607+
['hash', ['bin', 32, 32]],
608+
['signature', ['bin', 64, 64, 0, 127]],
609+
],
610+
],
611+
],
612+
],
613+
];
614+
const result = TemplateJson.gen(template) as any;
615+
expect(typeof result).toBe('object');
616+
expect(typeof result.name).toBe('string');
617+
expect(result.data instanceof Uint8Array).toBe(true);
618+
expect(result.data.length).toBe(3);
619+
expect(typeof result.metadata).toBe('object');
620+
expect(result.metadata.hash instanceof Uint8Array).toBe(true);
621+
expect(result.metadata.hash.length).toBe(32);
622+
expect(result.metadata.signature instanceof Uint8Array).toBe(true);
623+
expect(result.metadata.signature.length).toBe(64);
624+
// Check signature values are in the specified range
625+
for (let i = 0; i < result.metadata.signature.length; i++) {
626+
expect(result.metadata.signature[i]).toBeGreaterThanOrEqual(0);
627+
expect(result.metadata.signature[i]).toBeLessThanOrEqual(127);
628+
}
629+
});
452630
});
453631
});
454632

src/structured/types.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,29 @@ export type TemplateNode =
99
| LiteralTemplate
1010
| NumberTemplate
1111
| IntegerTemplate
12+
| Int64Template
1213
| FloatTemplate
1314
| StringTemplate
1415
| BooleanTemplate
16+
| BinTemplate
1517
| NullTemplate
1618
| ArrayTemplate
1719
| ObjectTemplate
1820
| MapTemplate
1921
| OrTemplate;
2022

21-
export type TemplateShorthand = 'num' | 'int' | 'float' | 'str' | 'bool' | 'nil' | 'arr' | 'obj' | 'map';
23+
export type TemplateShorthand =
24+
| 'num'
25+
| 'int'
26+
| 'int64'
27+
| 'float'
28+
| 'str'
29+
| 'bool'
30+
| 'bin'
31+
| 'nil'
32+
| 'arr'
33+
| 'obj'
34+
| 'map';
2235

2336
/**
2437
* Recursive reference allows for recursive template construction, for example:
@@ -59,6 +72,12 @@ export type NumberTemplate = [type: 'num', min?: number, max?: number];
5972
*/
6073
export type IntegerTemplate = [type: 'int', min?: number, max?: number];
6174

75+
/**
76+
* 64-bit integer template. Generates a random bigint within the specified range.
77+
* If no range is specified, it defaults to a reasonable range for 64-bit integers.
78+
*/
79+
export type Int64Template = [type: 'int64', min?: bigint, max?: bigint];
80+
6281
/**
6382
* Float template. Generates a random floating-point number within the specified
6483
* 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];
8099
*/
81100
export type BooleanTemplate = [type: 'bool', value?: boolean];
82101

102+
/**
103+
* Binary template. Generates a random Uint8Array. The template allows
104+
* specifying the length of binary data and the range of values in each octet.
105+
*/
106+
export type BinTemplate = [
107+
type: 'bin',
108+
/**
109+
* The minimum length of binary data. Defaults to 0.
110+
*/
111+
min?: number,
112+
/**
113+
* The maximum length of binary data. Defaults to 5.
114+
*/
115+
max?: number,
116+
/**
117+
* The minimum octet value. Defaults to 0.
118+
*/
119+
omin?: number,
120+
/**
121+
* The maximum octet value. Defaults to 255.
122+
*/
123+
omax?: number,
124+
];
125+
83126
/**
84127
* Null template. Generates a `null` value. If a specific value is provided, it
85128
* will always return that value; otherwise, it returns `null`.

0 commit comments

Comments
 (0)