Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: v8 support #759

Merged
merged 30 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9b365da
feat: support v6 uuids
broofa Jun 3, 2024
8aeee27
fix: test and comment
broofa Jun 3, 2024
6eebe89
test: add v1<->v6 utilities to browser tests
broofa Jun 4, 2024
a69f436
fix: filenames and lint flags
broofa Jun 4, 2024
f4cf3e8
chore: upgrade husky dependency
broofa Jun 4, 2024
4710dee
chore: revert bundlewatch config
broofa Jun 4, 2024
f7efb7f
feat: add v6 method
broofa Jun 4, 2024
f5c6018
fix: make browser test more resilient to test order
broofa Jun 4, 2024
18cff2e
chore: revise v6 implementation
broofa Jun 5, 2024
09e269d
docs: update with v6 API
broofa Jun 5, 2024
3c35893
chore: update v6 bundlewatch size
broofa Jun 5, 2024
d09e432
docs: v6 uuid
broofa Jun 5, 2024
b510bac
Merge branch 'main' into v1v6
broofa Jun 5, 2024
7d206b4
chore: v6 bundlewatch size
broofa Jun 5, 2024
e06069d
Merge branch 'main' into v1v6
broofa Jun 5, 2024
e94c947
fix: add .local/v6.js
broofa Jun 5, 2024
ac6bfdb
chore: gitignore vscode
broofa Jun 5, 2024
fdab55f
chore: rm vscode dir
broofa Jun 5, 2024
5df758e
chore: update comments
broofa Jun 5, 2024
4f13e5c
chore: v8 support (meaning improved unit tests)
broofa Jun 5, 2024
f3a94e2
docs: document v8() as deliberately not-implemented
broofa Jun 5, 2024
08d9303
fix: pr feedback
broofa Jun 5, 2024
0212b6e
docs: fix broken anchor refs
broofa Jun 5, 2024
3e12bf6
Merge branch 'v1v6' into v8
broofa Jun 5, 2024
fe81cbb
docs: typo
broofa Jun 5, 2024
604505e
Merge branch 'v1v6' into v8
broofa Jun 5, 2024
5d54310
docs: fix regression
broofa Jun 5, 2024
59978d7
Merge branch 'v1v6' into v8
broofa Jun 5, 2024
c851c4d
fix: eslint --fix
broofa Jun 5, 2024
c471e30
Merge branch 'main' into v8
broofa Jun 7, 2024
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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ...
| [`uuid.v6()`](#uuidv6options-buffer-offset) | Create a version 6 (timestamp, reordered) UUID | New in `uuid@10` |
| [`uuid.v6ToV1()`](#uuidv6tov1uuid) | Create a version 1 UUID from a version 6 UUID | New in `uuid@10` |
| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@10` |
| ~~[`uuid.v8()`](#uuidv8)~~ | "Intentionally left blank" | |
| [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `[email protected]` |
| [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `[email protected]` |

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

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

### ~~uuid.v8()~~

**_"Intentionally left blank"_**

<!-- prettier-ignore -->
> [!NOTE]
> 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.

### uuid.validate(str)

Test a string to see if it is a valid UUID
Expand Down
9 changes: 9 additions & 0 deletions README_js.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ For timestamp UUIDs, namespace UUIDs, and other options read on ...
| [`uuid.v6()`](#uuidv6options-buffer-offset) | Create a version 6 (timestamp, reordered) UUID | New in `uuid@10` |
| [`uuid.v6ToV1()`](#uuidv6tov1uuid) | Create a version 1 UUID from a version 6 UUID | New in `uuid@10` |
| [`uuid.v7()`](#uuidv7options-buffer-offset) | Create a version 7 (Unix Epoch time-based) UUID | New in `uuid@10` |
| ~~[`uuid.v8()`](#uuidv8)~~ | "Intentionally left blank" | |
| [`uuid.validate()`](#uuidvalidatestr) | Test a string to see if it is a valid UUID | New in `[email protected]` |
| [`uuid.version()`](#uuidversionstr) | Detect RFC version of a UUID | New in `[email protected]` |

Expand Down Expand Up @@ -361,6 +362,14 @@ import { v7 as uuidv7 } from 'uuid';
uuidv7(); // RESULT
```

### ~~uuid.v8()~~

**_"Intentionally left blank"_**

<!-- prettier-ignore -->
> [!NOTE]
> 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.

### uuid.validate(str)

Test a string to see if it is a valid UUID
Expand Down
111 changes: 111 additions & 0 deletions test/unit/test_constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import MAX from '../../src/max.js';
import NIL from '../../src/nil.js';

// Table of [uuid value, expected validate(), [expected version()]]
export const TESTS = [
// constants
{ value: NIL, expectedValidate: true, expectedVersion: 0 },
{ value: MAX, expectedValidate: true, expectedVersion: 15 },

// each version, with either all 0's or all 1's in settable bits
{ value: '00000000-0000-1000-8000-000000000000', expectedValidate: true, expectedVersion: 1 },
{ value: 'ffffffff-ffff-1fff-8fff-ffffffffffff', expectedValidate: true, expectedVersion: 1 },
{ value: '00000000-0000-2000-8000-000000000000', expectedValidate: true, expectedVersion: 2 },
{ value: 'ffffffff-ffff-2fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 2 },
{ value: '00000000-0000-3000-8000-000000000000', expectedValidate: true, expectedVersion: 3 },
{ value: 'ffffffff-ffff-3fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 3 },
{ value: '00000000-0000-4000-8000-000000000000', expectedValidate: true, expectedVersion: 4 },
{ value: 'ffffffff-ffff-4fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 4 },
{ value: '00000000-0000-5000-8000-000000000000', expectedValidate: true, expectedVersion: 5 },
{ value: 'ffffffff-ffff-5fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 5 },
{ value: '00000000-0000-6000-8000-000000000000', expectedValidate: true, expectedVersion: 6 },
{ value: 'ffffffff-ffff-6fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 6 },
{ value: '00000000-0000-7000-8000-000000000000', expectedValidate: true, expectedVersion: 7 },
{ value: 'ffffffff-ffff-7fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 7 },
{ value: '00000000-0000-8000-8000-000000000000', expectedValidate: true, expectedVersion: 8 },
{ value: 'ffffffff-ffff-8fff-bfff-ffffffffffff', expectedValidate: true, expectedVersion: 8 },
{ value: '00000000-0000-9000-8000-000000000000', expectedValidate: false },
{ value: 'ffffffff-ffff-9fff-bfff-ffffffffffff', expectedValidate: false },
{ value: '00000000-0000-a000-8000-000000000000', expectedValidate: false },
{ value: 'ffffffff-ffff-afff-bfff-ffffffffffff', expectedValidate: false },
{ value: '00000000-0000-b000-8000-000000000000', expectedValidate: false },
{ value: 'ffffffff-ffff-bfff-bfff-ffffffffffff', expectedValidate: false },
{ value: '00000000-0000-c000-8000-000000000000', expectedValidate: false },
{ value: 'ffffffff-ffff-cfff-bfff-ffffffffffff', expectedValidate: false },
{ value: '00000000-0000-d000-8000-000000000000', expectedValidate: false },
{ value: 'ffffffff-ffff-dfff-bfff-ffffffffffff', expectedValidate: false },
{ value: '00000000-0000-e000-8000-000000000000', expectedValidate: false },
{ value: 'ffffffff-ffff-efff-bfff-ffffffffffff', expectedValidate: false },

// selection of normal, valid UUIDs
{ value: 'd9428888-122b-11e1-b85c-61cd3cbb3210', expectedValidate: true, expectedVersion: 1 },
{ value: '000003e8-2363-21ef-b200-325096b39f47', expectedValidate: true, expectedVersion: 2 },
{ value: 'a981a0c2-68b1-35dc-bcfc-296e52ab01ec', expectedValidate: true, expectedVersion: 3 },
{ value: '109156be-c4fb-41ea-b1b4-efe1671c5836', expectedValidate: true, expectedVersion: 4 },
{ value: '90123e1c-7512-523e-bb28-76fab9f2f73d', expectedValidate: true, expectedVersion: 5 },
{ value: '1ef21d2f-1207-6660-8c4f-419efbd44d48', expectedValidate: true, expectedVersion: 6 },
{ value: '017f22e2-79b0-7cc3-98c4-dc0c0c07398f', expectedValidate: true, expectedVersion: 7 },
{ value: '0d8f23a0-697f-83ae-802e-48f3756dd581', expectedValidate: true, expectedVersion: 8 },

// all variant octet values
{ value: '00000000-0000-1000-0000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-1000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-2000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-3000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-4000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-5000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-6000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-7000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-8000-000000000000', expectedValidate: true, expectedVersion: 1 },
{ value: '00000000-0000-1000-9000-000000000000', expectedValidate: true, expectedVersion: 1 },
{ value: '00000000-0000-1000-a000-000000000000', expectedValidate: true, expectedVersion: 1 },
{ value: '00000000-0000-1000-b000-000000000000', expectedValidate: true, expectedVersion: 1 },
{ value: '00000000-0000-1000-c000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-d000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-e000-000000000000', expectedValidate: false },
{ value: '00000000-0000-1000-f000-000000000000', expectedValidate: false },

// invalid strings
{ value: '00000000000000000000000000000000', expectedValidate: false }, // unhyphenated NIL
{ value: '', expectedValidate: false },
{ value: 'invalid uuid string', expectedValidate: false },
{
value: '=Y00a-f*vb*-c-d#-p00f\b-g0h-#i^-j*3&-L00k-\nl---00n-fg000-00p-00r+',
expectedValidate: false,
},

// invalid types
{ value: undefined, expectedValidate: false },
{ value: null, expectedValidate: false },
{ value: 123, expectedValidate: false },
{ value: /regex/, expectedValidate: false },
{ value: new Date(0), expectedValidate: false },
{ value: false, expectedValidate: false },
];

// Add NIL and MAX UUIDs with 1-bit flipped in each position
for (let charIndex = 0; charIndex < 36; charIndex++) {
// Skip hyphens and version char
if (
charIndex === 8 ||
charIndex === 13 ||
charIndex === 14 || // version char
charIndex === 18 ||
charIndex === 23
) {
continue;
}

const nilChars = NIL.split('');
const maxChars = MAX.split('');

for (let i = 0; i < 4; i++) {
nilChars[charIndex] = (0x0 ^ (1 << i)).toString(16);
// NIL UUIDs w/ a single 1-bit
TESTS.push({ value: nilChars.join(''), expectedValidate: false });

// MAX UUIDs w/ a single 0-bit
maxChars[charIndex] = (0xf ^ (1 << i)).toString(16);
TESTS.push({ value: maxChars.join(''), expectedValidate: false });
}
}
71 changes: 7 additions & 64 deletions test/unit/validate.test.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,15 @@
import assert from 'assert';
import MAX from '../../src/max.js';
import NIL from '../../src/nil.js';
import validate from '../../src/validate.js';
import { TESTS } from './test_constants.js';

describe('validate', () => {
test('validate uuid', () => {
assert.strictEqual(validate(NIL), true);
assert.strictEqual(validate(MAX), true);

// test valid UUID versions

// v1
assert.strictEqual(validate('d9428888-122b-11e1-b85c-61cd3cbb3210'), true);

// v3
assert.strictEqual(validate('a981a0c2-68b1-35dc-bcfc-296e52ab01ec'), true);

// v4
assert.strictEqual(validate('109156be-c4fb-41ea-b1b4-efe1671c5836'), true);

// v5
assert.strictEqual(validate('90123e1c-7512-523e-bb28-76fab9f2f73d'), true);

// v6
assert.strictEqual(validate('1ef21d2f-1207-6660-8c4f-419efbd44d48'), true);

// v7
assert.strictEqual(validate('017f22e2-79b0-7cc3-98c4-dc0c0c07398f'), true);

// test invalid/unsupported UUID versions
[0, 2, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'].forEach((v) => {
describe('validate() tests', () => {
test('TESTS cases', () => {
for (const { value, expectedValidate } of TESTS) {
assert.strictEqual(
validate('12300000-0000-' + v + '000-0000-000000000000'),
false,
'version ' + v + ' should not be valid'
validate(value),
expectedValidate,
`validate(${value}) should be ${expectedValidate}`
);
});

assert.strictEqual(validate(), false);

assert.strictEqual(validate(''), false);

assert.strictEqual(validate('invalid uuid string'), false);

assert.strictEqual(validate('00000000000000000000000000000000'), false);

// NIL UUIDs that have a bit set (incorrectly) should not validate
for (let charIndex = 0; charIndex < 36; charIndex++) {
if (charIndex === 14) {
continue;
} // version field

for (let bit = 0; bit < 4; bit++) {
const chars = NIL.split('');
if (chars[charIndex] === '-') {
continue;
}

chars[charIndex] = (1 << bit).toString(16);
assert.strictEqual(validate(chars.join('')), false);
}
}

assert.strictEqual(
validate(
'=Y00a-f*v00b*-00c-00d#-p00f\b-00g-00h-####00i^^^-00j*1*2*3&-L00k-\n00l-/00m-----00n-fg000-00p-00r+'
),
false
);
});
});
31 changes: 12 additions & 19 deletions test/unit/version.test.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import assert from 'assert';
import MAX from '../../src/max.js';
import NIL from '../../src/nil.js';
import version from '../../src/version.js';
import { TESTS } from './test_constants.js';

describe('version', () => {
test('check uuid version', () => {
assert.strictEqual(version(NIL), 0);
assert.strictEqual(version(MAX), 15);

assert.strictEqual(version('d9428888-122b-11e1-b85c-61cd3cbb3210'), 1);
assert.strictEqual(version('a981a0c2-68b1-35dc-bcfc-296e52ab01ec'), 3);
assert.strictEqual(version('109156be-c4fb-41ea-b1b4-efe1671c5836'), 4);
assert.strictEqual(version('90123e1c-7512-523e-bb28-76fab9f2f73d'), 5);
assert.strictEqual(version('1ef21d2f-1207-6660-8c4f-419efbd44d48'), 6);
assert.strictEqual(version('017f22e2-79b0-7cc3-98c4-dc0c0c07398f'), 7);

assert.throws(() => version());
assert.throws(() => version(''));
assert.throws(() => version('invalid uuid string'));
assert.throws(() => version('00000000000000000000000000000000'));
assert.throws(() => version('=Y00a-f*v00b*-00c-00d#-p00f\b-00g-00h-##0p-00r+'));
describe('version() tests', () => {
test('TESTS cases', () => {
for (const { value, expectedValidate, expectedVersion } of TESTS) {
try {
const actualVersion = version(value);
assert(expectedValidate, `version(${value}) should throw`);
assert.strictEqual(actualVersion, expectedVersion);
} catch (err) {
assert(!expectedValidate, `version(${value}) threw unexpectedly`);
}
}
});
});
Loading