Skip to content

Instantly share code, notes, and snippets.

@click2install
Last active August 12, 2021 08:29
Show Gist options
  • Save click2install/f69ec65d70c75c72cea4daaf3f9cdc48 to your computer and use it in GitHub Desktop.
Save click2install/f69ec65d70c75c72cea4daaf3f9cdc48 to your computer and use it in GitHub Desktop.
[Guid] - Typescript Guid with a similar API to a C# Guid struct
import { expect } from "chai";
import { describe, it } from "mocha";
import * as faker from "faker";
import { Guid, GuidFormat } from "../Guid";
describe(nameof(Guid), () =>
{
const empty = "00000000-0000-0000-0000-000000000000";
describe(".newGuid()", () =>
{
it("should create a Guid", () =>
{
const actual = Guid.newGuid();
expect(actual).to.be.an.instanceof(Guid);
});
it("should create a v4 Guid", () =>
{
const guid = Guid.newGuid();
const actual = guid.toString().slice(14, 15);
expect(actual).to.equal("4");
});
it(`should not equal ${empty}`, () =>
{
const actual = Guid.newGuid();
expect(actual).to.not.equal(Guid.empty());
expect(actual.toString()).to.not.equal(empty);
});
});
describe(".empty()", () =>
{
it("should create a Guid", () =>
{
const actual = Guid.newGuid();
expect(actual).to.be.an.instanceof(Guid);
});
it(`should equal ${empty}`, () =>
{
const actual = Guid.empty();
expect(actual.toString()).to.equal(empty);
});
});
describe(".parse(value)", () =>
{
it(`should parse a valid guid string`, () =>
{
const guid = faker.random.uuid();
const actual = Guid.parse(guid);
expect(actual).to.be.an.instanceof(Guid);
expect(actual.toString()).to.equal(guid);
});
it(`should parse a ${nameof(Guid)} without dashes`, () =>
{
const expected = faker.random.uuid();
const actual = Guid.parse(expected.replace(/-/g, ""));
expect(actual).to.be.an.instanceof(Guid);
expect(actual.toString()).to.equal(expected);
});
it(`should throw when parsing an invalid ${nameof(Guid)}`, () =>
{
const actual = "zzzzzzzz-zzzz-4zzz-zzzz-zzzzzzzzzzzz";
expect(() => Guid.parse(actual)).to.throw(SyntaxError);
});
});
describe(".isGuid(value)", () =>
{
it(`should return true for an existing ${nameof(Guid)}`, () =>
{
const actual = Guid.isGuid(Guid.newGuid());
expect(actual).to.equal(true);
});
it(`should return true for a valid hex string`, () =>
{
const actual = Guid.isGuid(faker.random.uuid());
expect(actual).to.equal(true);
});
it(`should return true for a valid hex string without dashes`, () =>
{
const actual = Guid.isGuid(faker.random.uuid().replace(/-/g, ""));
expect(actual).to.equal(true);
});
it(`should return false for an invalid hex string`, () =>
{
const actual = Guid.isGuid("zzzzzzzz-zzzz-4zzz-zzzz-zzzzzzzzzzzz");
expect(actual).to.equal(false);
});
it(`should return false for an invalid string`, () =>
{
const actual = Guid.isGuid(faker.random.word());
expect(actual).to.equal(false);
});
});
describe(".equals(value)", () =>
{
it(`should return true for the same ${nameof(Guid)} instance`, () =>
{
const guid = Guid.newGuid();
const actual = guid.equals(guid);
expect(actual).to.equal(true);
});
it(`should return true for the different ${nameof(Guid)} instance with same value`, () =>
{
const left = Guid.newGuid();
const right = Guid.parse(left.toString());
const actual = left.equals(right);
expect(actual).to.equal(true);
});
it(`should return false when the ${nameof(Guid)} value is not the same`, () =>
{
const left = Guid.newGuid();
const right = Guid.newGuid();
const actual = left.equals(right);
expect(actual).to.equal(false);
});
});
describe(".toString(format)", () =>
{
it(`should return a dashed ${nameof(Guid)} when no arguments are given`, () =>
{
const uuid = faker.random.uuid();
const guid = Guid.parse(uuid);
const actual = guid.toString();
expect(actual).to.equal(uuid);
});
it(`should return a dashed ${nameof(Guid)} when the ${nameof.full(GuidFormat.Hyphens)} format is supplied`, () =>
{
const uuid = faker.random.uuid();
const guid = Guid.parse(uuid);
const actual = guid.toString(GuidFormat.Hyphens);
expect(actual).to.equal(uuid);
});
it(`should return a non-dashed ${nameof(Guid)} when the ${nameof.full(GuidFormat.Digits)} format is supplied`, () =>
{
const uuid = faker.random.uuid();
const guid = Guid.parse(uuid);
const actual = guid.toString(GuidFormat.Digits);
expect(actual).to.equal(uuid.replace(/-/g, ""));
});
it(`should return a braced ${nameof(Guid)} when the ${nameof.full(GuidFormat.Braces)} format is supplied`, () =>
{
const uuid = faker.random.uuid();
const guid = Guid.parse(uuid);
const actual = guid.toString(GuidFormat.Braces);
expect(actual).to.equal(`{${uuid}}`);
});
});
});
// NOTE: requires https://www.npmjs.com/package/ts-nameof
import { v4 as uuid, NIL, validate } from "uuid";
export class Guid
{
/**
* Returns an new v4 Guid as xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx.
*
* @static
* @public
* @returns Guid
*/
static newGuid(): Guid
{
return new Guid(uuid());
}
/**
* Returns an empty Guid as 00000000-0000-0000-0000-000000000000.
*
* @static
* @public
* @returns Guid
*/
static empty(): Guid
{
return new Guid(NIL);
}
/**
* Parses the given value to a Guid if possible, otherwise throws a SyntaxError.
*
* @static
* @public
* @param {string} value The value to parse to a Guid.
* @returns Guid
*/
static parse(value: string): Guid
{
if (validate(value))
{
return new Guid(value);
}
if (/^[a-f0-9]{32}$/gi.test(value))
{
const regex = /^([a-f0-9]{8})([a-f0-9]{4})([a-f0-9]{4})([a-f0-9]{4})([a-f0-9]{12})$/gi;
const guid = value.replace(regex, "$1-$2-$3-$4-$5");
if (validate(guid))
{
return new Guid(guid);
}
}
throw new SyntaxError(`Unknown format to create ${nameof(Guid)}.`);
}
/**
* Returns a boolean indicating if the given value is a valid Guid.
*
* @static
* @public
* @param {Guid|string} value The value to check for being a valid Guid.
* @returns boolean
*/
static isGuid(value: Guid | string): boolean
{
try
{
Guid.parse(value.toString());
return true;
}
catch
{
return false;
}
}
#value: string;
// private ctor so all Guid are created with the factory functions newGuid or empty.
private constructor(value: string | Guid)
{
this.#value = value.toString();
}
/**
* Returns a boolean indicating whether or not the Guid values are equivalent.
* Equivalency is based on the Guid value, not reference equality.
*
* @public
* @param {Guid} value
* @memberof Guid
* @returns boolean
*/
equals(value: Guid): boolean
{
return value.#value === this.#value;
}
/**
* Returns a string representation of the Guid.
*
* @public
* @param {GuidFormat} [format="D"]
* @memberof Guid
* @returns string
*/
toString(format: GuidFormat = GuidFormat.Hyphens): string
{
const formatter =
{
[GuidFormat.Hyphens]: () => this.#value.toLowerCase(),
[GuidFormat.Digits]: () => this.#value.replace(/-/gi, "").toLowerCase(),
[GuidFormat.Braces]: () => `{${this.#value.toLowerCase()}}`
}[format];
return formatter();
}
}
/**
* Enum for formatting a Guid string.
*
* @public
* @readonly
* @enum {string}
*/
export enum GuidFormat
{
/**
* Lowercase standard v4 Guid as 32 digits separated by hyphens: xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx
*/
Hyphens = "D",
/**
* Lowercase standard v4 Guid as 32 digits: xxxxxxxxxxxx4xxxxxxxxxxxxxxxxxxx
*/
Digits = "N",
/**
* Lowercase standard v4 Guid as 32 digits separated by hyphens, enclosed in braces: {xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx}
*/
Braces = "B"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment