mirror of
https://github.com/denoland/deno.git
synced 2024-11-26 16:09:27 -05:00
feat(std/uuid): Implement uuid v5 (#4916)
This commit is contained in:
parent
516d970fd3
commit
df0000ff0a
9 changed files with 180 additions and 11 deletions
|
@ -47,9 +47,9 @@ export class Sha1 {
|
||||||
this._finalized = this._hashed = false;
|
this._finalized = this._hashed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data: string | ArrayBuffer | ArrayBufferView): void {
|
update(data: string | ArrayBuffer | ArrayBufferView): Sha1 {
|
||||||
if (this._finalized) {
|
if (this._finalized) {
|
||||||
return;
|
return this;
|
||||||
}
|
}
|
||||||
let notString = true;
|
let notString = true;
|
||||||
let message;
|
let message;
|
||||||
|
@ -119,6 +119,7 @@ export class Sha1 {
|
||||||
this._hBytes += (this._bytes / 4294967296) >>> 0;
|
this._hBytes += (this._bytes / 4294967296) >>> 0;
|
||||||
this._bytes = this._bytes >>> 0;
|
this._bytes = this._bytes >>> 0;
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
finalize(): void {
|
finalize(): void {
|
|
@ -3,20 +3,20 @@ const { test } = Deno;
|
||||||
import { assertEquals } from "../testing/asserts.ts";
|
import { assertEquals } from "../testing/asserts.ts";
|
||||||
import { Sha1 } from "./sha1.ts";
|
import { Sha1 } from "./sha1.ts";
|
||||||
|
|
||||||
test("[ws/sha] test1", () => {
|
test("[util/sha] test1", () => {
|
||||||
const sha1 = new Sha1();
|
const sha1 = new Sha1();
|
||||||
sha1.update("abcde");
|
sha1.update("abcde");
|
||||||
assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("[ws/sha] testWithArray", () => {
|
test("[util/sha] testWithArray", () => {
|
||||||
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
|
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
|
||||||
const sha1 = new Sha1();
|
const sha1 = new Sha1();
|
||||||
sha1.update(data);
|
sha1.update(data);
|
||||||
assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("[ws/sha] testSha1WithBuffer", () => {
|
test("[util/sha] testSha1WithBuffer", () => {
|
||||||
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
|
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
|
||||||
const sha1 = new Sha1();
|
const sha1 = new Sha1();
|
||||||
sha1.update(data.buffer);
|
sha1.update(data.buffer);
|
|
@ -13,6 +13,35 @@ export function bytesToUuid(bytes: number[] | Uint8Array): string {
|
||||||
"-",
|
"-",
|
||||||
...bits.slice(8, 10),
|
...bits.slice(8, 10),
|
||||||
"-",
|
"-",
|
||||||
...bits.slice(10),
|
...bits.slice(10, 16),
|
||||||
].join("");
|
].join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uuidToBytes(uuid: string): number[] {
|
||||||
|
const bytes: number[] = [];
|
||||||
|
|
||||||
|
uuid.replace(/[a-fA-F0-9]{2}/g, (hex: string): string => {
|
||||||
|
bytes.push(parseInt(hex, 16));
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringToBytes(str: string): number[] {
|
||||||
|
str = unescape(encodeURIComponent(str));
|
||||||
|
const bytes = new Array(str.length);
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
bytes[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBuffer(content: number[]): ArrayBuffer {
|
||||||
|
const arrayBuffer = new ArrayBuffer(content.length);
|
||||||
|
const uint8Array = new Uint8Array(arrayBuffer);
|
||||||
|
for (let i = 0; i < content.length; i++) {
|
||||||
|
uint8Array[i] = content[i];
|
||||||
|
}
|
||||||
|
return arrayBuffer;
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
import * as v1 from "./v1.ts";
|
import * as v1 from "./v1.ts";
|
||||||
import * as v4 from "./v4.ts";
|
import * as v4 from "./v4.ts";
|
||||||
|
import * as v5 from "./v5.ts";
|
||||||
|
|
||||||
export const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
export const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
||||||
|
|
||||||
|
@ -21,7 +22,4 @@ const NOT_IMPLEMENTED = {
|
||||||
// TODO Implement
|
// TODO Implement
|
||||||
export const v3 = NOT_IMPLEMENTED;
|
export const v3 = NOT_IMPLEMENTED;
|
||||||
|
|
||||||
export { v1, v4 };
|
export { v1, v4, v5 };
|
||||||
|
|
||||||
// TODO Implement
|
|
||||||
export const v5 = NOT_IMPLEMENTED;
|
|
||||||
|
|
|
@ -11,3 +11,7 @@ import "./tests/v1/generate.ts";
|
||||||
// V4 Tests
|
// V4 Tests
|
||||||
import "./tests/v4/validate.ts";
|
import "./tests/v4/validate.ts";
|
||||||
import "./tests/v4/generate.ts";
|
import "./tests/v4/generate.ts";
|
||||||
|
|
||||||
|
// V5 Tests
|
||||||
|
import "./tests/v5/validate.ts";
|
||||||
|
import "./tests/v5/generate.ts";
|
||||||
|
|
66
std/uuid/tests/v5/generate.ts
Normal file
66
std/uuid/tests/v5/generate.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import { assert, assertEquals } from "../../../testing/asserts.ts";
|
||||||
|
const { test } = Deno;
|
||||||
|
import { generate, validate } from "../../v5.ts";
|
||||||
|
const NAMESPACE = "1b671a64-40d5-491e-99b0-da01ff1f3341";
|
||||||
|
test({
|
||||||
|
name: "[UUID] test_uuid_v5",
|
||||||
|
fn(): void {
|
||||||
|
const u = generate({ value: "", namespace: NAMESPACE });
|
||||||
|
assertEquals(typeof u, "string", "returns a string");
|
||||||
|
assert(u !== "", "return string is not empty");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "[UUID] test_uuid_v5_format",
|
||||||
|
fn(): void {
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
const u = generate({ value: String(i), namespace: NAMESPACE }) as string;
|
||||||
|
assert(validate(u), `${u} is not a valid uuid v5`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "[UUID] test_uuid_v5_option",
|
||||||
|
fn(): void {
|
||||||
|
const v5Options = {
|
||||||
|
value: "Hello, World",
|
||||||
|
namespace: NAMESPACE,
|
||||||
|
};
|
||||||
|
const u = generate(v5Options);
|
||||||
|
assertEquals(u, "4b4f2adc-5b27-57b5-8e3a-c4c4bcf94f05");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test({
|
||||||
|
name: "[UUID] test_uuid_v5_buf_offset",
|
||||||
|
fn(): void {
|
||||||
|
const buf = [
|
||||||
|
75,
|
||||||
|
79,
|
||||||
|
42,
|
||||||
|
220,
|
||||||
|
91,
|
||||||
|
39,
|
||||||
|
87,
|
||||||
|
181,
|
||||||
|
142,
|
||||||
|
58,
|
||||||
|
196,
|
||||||
|
196,
|
||||||
|
188,
|
||||||
|
249,
|
||||||
|
79,
|
||||||
|
5,
|
||||||
|
];
|
||||||
|
const origin = JSON.parse(JSON.stringify(buf));
|
||||||
|
generate({ value: "Hello, World", namespace: NAMESPACE }, buf);
|
||||||
|
assertEquals(origin, buf);
|
||||||
|
|
||||||
|
generate({ value: "Hello, World", namespace: NAMESPACE }, buf, 3);
|
||||||
|
assertEquals(origin.slice(0, 3), buf.slice(0, 3));
|
||||||
|
assertEquals(origin, buf.slice(3));
|
||||||
|
},
|
||||||
|
});
|
19
std/uuid/tests/v5/validate.ts
Normal file
19
std/uuid/tests/v5/validate.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import { assert } from "../../../testing/asserts.ts";
|
||||||
|
import { generate, validate } from "../../v5.ts";
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "[UUID] is_valid_uuid_v5",
|
||||||
|
fn(): void {
|
||||||
|
const u = generate({
|
||||||
|
value: "Hello, World",
|
||||||
|
namespace: "1b671a64-40d5-491e-99b0-da01ff1f3341",
|
||||||
|
}) as string;
|
||||||
|
const t = "4b4f2adc-5b27-57b5-8e3a-c4c4bcf94f05";
|
||||||
|
const n = "4b4f2adc-5b27-17b5-8e3a-c4c4bcf94f05";
|
||||||
|
|
||||||
|
assert(validate(u), `generated ${u} should be valid`);
|
||||||
|
assert(validate(t), `${t} should be valid`);
|
||||||
|
assert(!validate(n), `${n} should not be valid`);
|
||||||
|
},
|
||||||
|
});
|
52
std/uuid/v5.ts
Normal file
52
std/uuid/v5.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
import {
|
||||||
|
bytesToUuid,
|
||||||
|
createBuffer,
|
||||||
|
stringToBytes,
|
||||||
|
uuidToBytes,
|
||||||
|
} from "./_common.ts";
|
||||||
|
import { Sha1 } from "../util/sha1.ts";
|
||||||
|
import { isString } from "../node/util.ts";
|
||||||
|
import { assert } from "../testing/asserts.ts";
|
||||||
|
|
||||||
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
|
||||||
|
export function validate(id: string): boolean {
|
||||||
|
return UUID_RE.test(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface V5Options {
|
||||||
|
value: string | number[];
|
||||||
|
namespace: string | number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generate(
|
||||||
|
options: V5Options,
|
||||||
|
buf?: number[],
|
||||||
|
offset?: number
|
||||||
|
): string | number[] {
|
||||||
|
const i = (buf && offset) || 0;
|
||||||
|
|
||||||
|
let { value, namespace } = options;
|
||||||
|
if (isString(value)) value = stringToBytes(value as string);
|
||||||
|
if (isString(namespace)) namespace = uuidToBytes(namespace as string);
|
||||||
|
assert(
|
||||||
|
namespace.length === 16,
|
||||||
|
"namespace must be uuid string or an Array of 16 byte values"
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = (namespace as number[]).concat(value as number[]);
|
||||||
|
const bytes = new Sha1().update(createBuffer(content)).digest();
|
||||||
|
|
||||||
|
bytes[6] = (bytes[6] & 0x0f) | 0x50;
|
||||||
|
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||||
|
|
||||||
|
if (buf) {
|
||||||
|
for (let idx = 0; idx < 16; ++idx) {
|
||||||
|
buf[i + idx] = bytes[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf || bytesToUuid(bytes);
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import { decode, encode } from "../encoding/utf8.ts";
|
||||||
import { hasOwnProperty } from "../util/has_own_property.ts";
|
import { hasOwnProperty } from "../util/has_own_property.ts";
|
||||||
import { BufReader, BufWriter } from "../io/bufio.ts";
|
import { BufReader, BufWriter } from "../io/bufio.ts";
|
||||||
import { readLong, readShort, sliceLongToBytes } from "../io/ioutil.ts";
|
import { readLong, readShort, sliceLongToBytes } from "../io/ioutil.ts";
|
||||||
import { Sha1 } from "./sha1.ts";
|
import { Sha1 } from "../util/sha1.ts";
|
||||||
import { writeResponse } from "../http/io.ts";
|
import { writeResponse } from "../http/io.ts";
|
||||||
import { TextProtoReader } from "../textproto/mod.ts";
|
import { TextProtoReader } from "../textproto/mod.ts";
|
||||||
import { Deferred, deferred } from "../util/async.ts";
|
import { Deferred, deferred } from "../util/async.ts";
|
||||||
|
|
Loading…
Reference in a new issue