Skip to content

Commit 35a5342

Browse files
authoredJun 7, 2024··
feat: v8 support (#759)
1 parent c4ed13e commit 35a5342

File tree

5 files changed

+149
-84
lines changed

5 files changed

+149
-84
lines changed
 

‎README.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ...
6868
| [`uuid.v6()`](#uuidv6options-buffer-offset) | Create a version 6 (timestamp, reordered) UUID | New in `uuid@10` |
6969
| [`uuid.v6ToV1()`](#uuidv6tov1uuid) | Create a version 1 UUID from a version 6 UUID | New in `uuid@10` |
7070
| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@10` |
71+
| ~~[`uuid.v8()`](#uuidv8)~~ | "Intentionally left blank" | |
7172
| [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.3` |
7273
| [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.3` |
7374

@@ -120,7 +121,7 @@ import { parse as uuidParse } from 'uuid';
120121
const bytes = uuidParse('6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b');
121122

122123
// Convert to hex strings to show byte order (for documentation purposes)
123-
[...bytes].map((v) => v.toString(16).padStart(2, '0')); //
124+
[...bytes].map((v) => v.toString(16).padStart(2, '0')); //
124125
// [
125126
// '6e', 'c0', 'bd', '7f',
126127
// '11', 'c0', '43', 'da',
@@ -353,6 +354,14 @@ import { v7 as uuidv7 } from 'uuid';
353354
uuidv7(); // ⇨ '01695553-c90c-722d-9b5d-b38dfbbd4bed'
354355
```
355356

357+
### ~~uuid.v8()~~
358+
359+
**_"Intentionally left blank"_**
360+
361+
<!-- prettier-ignore -->
362+
> [!NOTE]
363+
> Version 8 (experimental) UUIDs are "[for experimental or vendor-specific use cases](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-8)". The RFC does not define a creation algorithm for them, which is why this package does not offer a `v8()` method. The `validate()` and `version()` methods do work with such UUIDs, however.
364+
356365
### uuid.validate(str)
357366

358367
Test a string to see if it is a valid UUID

‎README_js.md

+9
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ...
8282
| [`uuid.v6()`](#uuidv6options-buffer-offset) | Create a version 6 (timestamp, reordered) UUID | New in `uuid@10` |
8383
| [`uuid.v6ToV1()`](#uuidv6tov1uuid) | Create a version 1 UUID from a version 6 UUID | New in `uuid@10` |
8484
| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@10` |
85+
| ~~[`uuid.v8()`](#uuidv8)~~ | "Intentionally left blank" | |
8586
| [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `uuid@8.3` |
8687
| [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `uuid@8.3` |
8788

@@ -361,6 +362,14 @@ import { v7 as uuidv7 } from 'uuid';
361362
uuidv7(); // RESULT
362363
```
363364

365+
### ~~uuid.v8()~~
366+
367+
**_"Intentionally left blank"_**
368+
369+
<!-- prettier-ignore -->
370+
> [!NOTE]
371+
> Version 8 (experimental) UUIDs are "[for experimental or vendor-specific use cases](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-8)". The RFC does not define a creation algorithm for them, which is why this package does not offer a `v8()` method. The `validate()` and `version()` methods do work with such UUIDs, however.
372+
364373
### uuid.validate(str)
365374

366375
Test a string to see if it is a valid UUID

‎test/unit/test_constants.js

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import MAX from '../../src/max.js';
2+
import NIL from '../../src/nil.js';
3+
4+
// Table of [uuid value, expected validate(), [expected version()]]
5+
export const TESTS = [
6+
// constants
7+
{ value: NIL, expectedValidate: true, expectedVersion: 0 },
8+
{ value: MAX, expectedValidate: true, expectedVersion: 15 },
9+
10+
// each version, with either all 0's or all 1's in settable bits
11+
{ value: '00000000-0000-1000-8000-000000000000', expectedValidate: true, expectedVersion: 1 },
12+
{ value: 'ffffffff-ffff-1fff-8fff-ffffffffffff', expectedValidate: true, expectedVersion: 1 },
13+
{ value: '00000000-0000-2000-8000-000000000000', expectedValidate: true, expectedVersion: 2 },
14+
{ value: 'ffffffff-ffff-2fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 2 },
15+
{ value: '00000000-0000-3000-8000-000000000000', expectedValidate: true, expectedVersion: 3 },
16+
{ value: 'ffffffff-ffff-3fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 3 },
17+
{ value: '00000000-0000-4000-8000-000000000000', expectedValidate: true, expectedVersion: 4 },
18+
{ value: 'ffffffff-ffff-4fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 4 },
19+
{ value: '00000000-0000-5000-8000-000000000000', expectedValidate: true, expectedVersion: 5 },
20+
{ value: 'ffffffff-ffff-5fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 5 },
21+
{ value: '00000000-0000-6000-8000-000000000000', expectedValidate: true, expectedVersion: 6 },
22+
{ value: 'ffffffff-ffff-6fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 6 },
23+
{ value: '00000000-0000-7000-8000-000000000000', expectedValidate: true, expectedVersion: 7 },
24+
{ value: 'ffffffff-ffff-7fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 7 },
25+
{ value: '00000000-0000-8000-8000-000000000000', expectedValidate: true, expectedVersion: 8 },
26+
{ value: 'ffffffff-ffff-8fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 8 },
27+
{ value: '00000000-0000-9000-8000-000000000000', expectedValidate: false },
28+
{ value: 'ffffffff-ffff-9fff-bfff-ffffffffffff', expectedValidate: false },
29+
{ value: '00000000-0000-a000-8000-000000000000', expectedValidate: false },
30+
{ value: 'ffffffff-ffff-afff-bfff-ffffffffffff', expectedValidate: false },
31+
{ value: '00000000-0000-b000-8000-000000000000', expectedValidate: false },
32+
{ value: 'ffffffff-ffff-bfff-bfff-ffffffffffff', expectedValidate: false },
33+
{ value: '00000000-0000-c000-8000-000000000000', expectedValidate: false },
34+
{ value: 'ffffffff-ffff-cfff-bfff-ffffffffffff', expectedValidate: false },
35+
{ value: '00000000-0000-d000-8000-000000000000', expectedValidate: false },
36+
{ value: 'ffffffff-ffff-dfff-bfff-ffffffffffff', expectedValidate: false },
37+
{ value: '00000000-0000-e000-8000-000000000000', expectedValidate: false },
38+
{ value: 'ffffffff-ffff-efff-bfff-ffffffffffff', expectedValidate: false },
39+
40+
// selection of normal, valid UUIDs
41+
{ value: 'd9428888-122b-11e1-b85c-61cd3cbb3210', expectedValidate: true, expectedVersion: 1 },
42+
{ value: '000003e8-2363-21ef-b200-325096b39f47', expectedValidate: true, expectedVersion: 2 },
43+
{ value: 'a981a0c2-68b1-35dc-bcfc-296e52ab01ec', expectedValidate: true, expectedVersion: 3 },
44+
{ value: '109156be-c4fb-41ea-b1b4-efe1671c5836', expectedValidate: true, expectedVersion: 4 },
45+
{ value: '90123e1c-7512-523e-bb28-76fab9f2f73d', expectedValidate: true, expectedVersion: 5 },
46+
{ value: '1ef21d2f-1207-6660-8c4f-419efbd44d48', expectedValidate: true, expectedVersion: 6 },
47+
{ value: '017f22e2-79b0-7cc3-98c4-dc0c0c07398f', expectedValidate: true, expectedVersion: 7 },
48+
{ value: '0d8f23a0-697f-83ae-802e-48f3756dd581', expectedValidate: true, expectedVersion: 8 },
49+
50+
// all variant octet values
51+
{ value: '00000000-0000-1000-0000-000000000000', expectedValidate: false },
52+
{ value: '00000000-0000-1000-1000-000000000000', expectedValidate: false },
53+
{ value: '00000000-0000-1000-2000-000000000000', expectedValidate: false },
54+
{ value: '00000000-0000-1000-3000-000000000000', expectedValidate: false },
55+
{ value: '00000000-0000-1000-4000-000000000000', expectedValidate: false },
56+
{ value: '00000000-0000-1000-5000-000000000000', expectedValidate: false },
57+
{ value: '00000000-0000-1000-6000-000000000000', expectedValidate: false },
58+
{ value: '00000000-0000-1000-7000-000000000000', expectedValidate: false },
59+
{ value: '00000000-0000-1000-8000-000000000000', expectedValidate: true, expectedVersion: 1 },
60+
{ value: '00000000-0000-1000-9000-000000000000', expectedValidate: true, expectedVersion: 1 },
61+
{ value: '00000000-0000-1000-a000-000000000000', expectedValidate: true, expectedVersion: 1 },
62+
{ value: '00000000-0000-1000-b000-000000000000', expectedValidate: true, expectedVersion: 1 },
63+
{ value: '00000000-0000-1000-c000-000000000000', expectedValidate: false },
64+
{ value: '00000000-0000-1000-d000-000000000000', expectedValidate: false },
65+
{ value: '00000000-0000-1000-e000-000000000000', expectedValidate: false },
66+
{ value: '00000000-0000-1000-f000-000000000000', expectedValidate: false },
67+
68+
// invalid strings
69+
{ value: '00000000000000000000000000000000', expectedValidate: false }, // unhyphenated NIL
70+
{ value: '', expectedValidate: false },
71+
{ value: 'invalid uuid string', expectedValidate: false },
72+
{
73+
value: '=Y00a-f*vb*-c-d#-p00f\b-g0h-#i^-j*3&-L00k-\nl---00n-fg000-00p-00r+',
74+
expectedValidate: false,
75+
},
76+
77+
// invalid types
78+
{ value: undefined, expectedValidate: false },
79+
{ value: null, expectedValidate: false },
80+
{ value: 123, expectedValidate: false },
81+
{ value: /regex/, expectedValidate: false },
82+
{ value: new Date(0), expectedValidate: false },
83+
{ value: false, expectedValidate: false },
84+
];
85+
86+
// Add NIL and MAX UUIDs with 1-bit flipped in each position
87+
for (let charIndex = 0; charIndex < 36; charIndex++) {
88+
// Skip hyphens and version char
89+
if (
90+
charIndex === 8 ||
91+
charIndex === 13 ||
92+
charIndex === 14 || // version char
93+
charIndex === 18 ||
94+
charIndex === 23
95+
) {
96+
continue;
97+
}
98+
99+
const nilChars = NIL.split('');
100+
const maxChars = MAX.split('');
101+
102+
for (let i = 0; i < 4; i++) {
103+
nilChars[charIndex] = (0x0 ^ (1 << i)).toString(16);
104+
// NIL UUIDs w/ a single 1-bit
105+
TESTS.push({ value: nilChars.join(''), expectedValidate: false });
106+
107+
// MAX UUIDs w/ a single 0-bit
108+
maxChars[charIndex] = (0xf ^ (1 << i)).toString(16);
109+
TESTS.push({ value: maxChars.join(''), expectedValidate: false });
110+
}
111+
}

‎test/unit/validate.test.js

+7-64
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,15 @@
11
import assert from 'assert';
2-
import MAX from '../../src/max.js';
3-
import NIL from '../../src/nil.js';
42
import validate from '../../src/validate.js';
3+
import { TESTS } from './test_constants.js';
54

6-
describe('validate', () => {
7-
test('validate uuid', () => {
8-
assert.strictEqual(validate(NIL), true);
9-
assert.strictEqual(validate(MAX), true);
10-
11-
// test valid UUID versions
12-
13-
// v1
14-
assert.strictEqual(validate('d9428888-122b-11e1-b85c-61cd3cbb3210'), true);
15-
16-
// v3
17-
assert.strictEqual(validate('a981a0c2-68b1-35dc-bcfc-296e52ab01ec'), true);
18-
19-
// v4
20-
assert.strictEqual(validate('109156be-c4fb-41ea-b1b4-efe1671c5836'), true);
21-
22-
// v5
23-
assert.strictEqual(validate('90123e1c-7512-523e-bb28-76fab9f2f73d'), true);
24-
25-
// v6
26-
assert.strictEqual(validate('1ef21d2f-1207-6660-8c4f-419efbd44d48'), true);
27-
28-
// v7
29-
assert.strictEqual(validate('017f22e2-79b0-7cc3-98c4-dc0c0c07398f'), true);
30-
31-
// test invalid/unsupported UUID versions
32-
[0, 2, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'].forEach((v) => {
5+
describe('validate() tests', () => {
6+
test('TESTS cases', () => {
7+
for (const { value, expectedValidate } of TESTS) {
338
assert.strictEqual(
34-
validate('12300000-0000-' + v + '000-0000-000000000000'),
35-
false,
36-
'version ' + v + ' should not be valid'
9+
validate(value),
10+
expectedValidate,
11+
`validate(${value}) should be ${expectedValidate}`
3712
);
38-
});
39-
40-
assert.strictEqual(validate(), false);
41-
42-
assert.strictEqual(validate(''), false);
43-
44-
assert.strictEqual(validate('invalid uuid string'), false);
45-
46-
assert.strictEqual(validate('00000000000000000000000000000000'), false);
47-
48-
// NIL UUIDs that have a bit set (incorrectly) should not validate
49-
for (let charIndex = 0; charIndex < 36; charIndex++) {
50-
if (charIndex === 14) {
51-
continue;
52-
} // version field
53-
54-
for (let bit = 0; bit < 4; bit++) {
55-
const chars = NIL.split('');
56-
if (chars[charIndex] === '-') {
57-
continue;
58-
}
59-
60-
chars[charIndex] = (1 << bit).toString(16);
61-
assert.strictEqual(validate(chars.join('')), false);
62-
}
6313
}
64-
65-
assert.strictEqual(
66-
validate(
67-
'=Y00a-f*v00b*-00c-00d#-p00f\b-00g-00h-####00i^^^-00j*1*2*3&-L00k-\n00l-/00m-----00n-fg000-00p-00r+'
68-
),
69-
false
70-
);
7114
});
7215
});

‎test/unit/version.test.js

+12-19
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
import assert from 'assert';
2-
import MAX from '../../src/max.js';
3-
import NIL from '../../src/nil.js';
42
import version from '../../src/version.js';
3+
import { TESTS } from './test_constants.js';
54

6-
describe('version', () => {
7-
test('check uuid version', () => {
8-
assert.strictEqual(version(NIL), 0);
9-
assert.strictEqual(version(MAX), 15);
10-
11-
assert.strictEqual(version('d9428888-122b-11e1-b85c-61cd3cbb3210'), 1);
12-
assert.strictEqual(version('a981a0c2-68b1-35dc-bcfc-296e52ab01ec'), 3);
13-
assert.strictEqual(version('109156be-c4fb-41ea-b1b4-efe1671c5836'), 4);
14-
assert.strictEqual(version('90123e1c-7512-523e-bb28-76fab9f2f73d'), 5);
15-
assert.strictEqual(version('1ef21d2f-1207-6660-8c4f-419efbd44d48'), 6);
16-
assert.strictEqual(version('017f22e2-79b0-7cc3-98c4-dc0c0c07398f'), 7);
17-
18-
assert.throws(() => version());
19-
assert.throws(() => version(''));
20-
assert.throws(() => version('invalid uuid string'));
21-
assert.throws(() => version('00000000000000000000000000000000'));
22-
assert.throws(() => version('=Y00a-f*v00b*-00c-00d#-p00f\b-00g-00h-##0p-00r+'));
5+
describe('version() tests', () => {
6+
test('TESTS cases', () => {
7+
for (const { value, expectedValidate, expectedVersion } of TESTS) {
8+
try {
9+
const actualVersion = version(value);
10+
assert(expectedValidate, `version(${value}) should throw`);
11+
assert.strictEqual(actualVersion, expectedVersion);
12+
} catch (err) {
13+
assert(!expectedValidate, `version(${value}) threw unexpectedly`);
14+
}
15+
}
2316
});
2417
});

0 commit comments

Comments
 (0)
Please sign in to comment.