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

Fixes GUID/UUID generation to adhere to RFC 4122 UUID v4 specification #22

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0be5ad4
Update the validation regex to properly test for v4 version/variant f…
bradkovach Sep 28, 2022
5b2b69f
add toObject() method and deprecate toJSON() with warning since toJSO…
bradkovach Sep 28, 2022
b374d4c
Remove repetition in static create method to use static raw
bradkovach Sep 28, 2022
cc32d80
Update TypeError message to reference variable name.
bradkovach Sep 28, 2022
7f61657
Add return type to static gen(count) method
bradkovach Sep 28, 2022
3d8dcc8
Set VERSION VARIANTS and validator fields to read only
bradkovach Sep 28, 2022
877240a
Use singlq quotes wherever possible and format for line width
bradkovach Sep 28, 2022
2b19430
Update the raw() method to modify the version and variant digit to co…
bradkovach Sep 28, 2022
d40f0f0
Remove unnecessary chars in empty guid initializer
bradkovach Sep 28, 2022
44c2122
Clean up white space at EOF
bradkovach Sep 28, 2022
b6d09cc
Add short circuit to pass empty guid in isGuid
bradkovach Sep 29, 2022
eef9d1f
Update empty/null guid init logic
bradkovach Sep 29, 2022
559946a
Clean up test imports and use single quotes wherever possible
bradkovach Sep 29, 2022
89bc5f2
Add tests for empty/null
bradkovach Sep 29, 2022
5371408
Update package.json to use npx instead of hard links to node_modules
bradkovach Sep 29, 2022
95c12e9
Update lockfileVersion to 2
bradkovach Sep 29, 2022
a8f658e
1.0.9
bradkovach Sep 29, 2022
0d72237
Update README.md
bradkovach Sep 29, 2022
08da1a7
1.1.0
bradkovach Sep 29, 2022
38d0ac9
Add test and doc for isGuidLike test
bradkovach Sep 29, 2022
a3f9828
Add tests and nyc/istanbul to achieve 100% coverage
bradkovach Sep 29, 2022
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
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
dist
node_modules/
dist/
.nyc_output/
20 changes: 20 additions & 0 deletions .nycrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"nyc": {
"extends": "@istanbuljs/nyc-config-typescript",
"check-coverage": true,
"all": true,
"include": [
"tests/**/*.test.ts"
],
"exclude": [
"src/_tests_/**/*.*"
],
"reporter": [
"html",
"lcov",
"text",
"text-summary"
],
"report-dir": "coverage"
}
}
57 changes: 38 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
# Guid Typescript

Guid Typescript is a library that lets you generate guid code
Guid Typescript is a library that generates valid v4 GUID/UUID strings.

## Changelog

### Version 1.0.9

- Library now generates valid v4 GUID/UUID values. Some GUID/UUID parsers were causing parser errors because version/variant fields were not set properly.
- Tests added to verify v4 GUID/UUID generation and validation

#### Deprecated

- The `toJSON()` method should be replaced with a call to `toObject()`. The `toJSON()` did not produce JSON. This method should be eliminated in version 1.1.0. Any uses of `toJSON()` will produce a console warning.

#### Breaking Changes

- The `isGuid(maybeGuid)` method will no longer validate most (> 98%) of GUID/UUID values generated by this library in the past. If you need to validate GUID/UUID values generated by this library in the past, use the `isGuidLike(maybeGuidLike)` function.

## Installation and usage

### Installation

```
```bash
npm i guid-typescript --save
```

Expand All @@ -15,25 +31,28 @@ npm i guid-typescript --save
import { Guid } from "guid-typescript";

export class Example {
public id: Guid;
constructor() {
this.id = Guid.create(); // ==> b77d409a-10cd-4a47-8e94-b0cd0ab50aa1
}
public id: Guid;
constructor() {
this.id = Guid.create(); // ==> b77d409a-10cd-4a47-8e94-b0cd0ab50aa1
}
}
```

## Props and Methods

| Method/Prop | Description | Test | Status |
|---|---|---|---|
| static isGuid (guid: any): boolean | Check if value is a guid code | OK | Ready |
| static create ( ): Guid | Create a new guid | OK | Ready |
| static createEmpty ( ): Guid | Create an empty guid | OK | Ready |
| static parse (guid: string): Guid | Creates a guid instance from a given guid as string | OK | Ready |
| static raw ( ): string | Create a guid code in string format | OK | Ready |
| equals (other: Guid): boolean | Compare a guid code | OK | Ready |
| isEmpty ( ): boolean | Validate if a guid is empty | OK | Ready |
| toString ( ): string | Parse a guid instance to string format | OK | Ready |
| toJSON ( ): any | Parse to JSON format | OK | Ready |


| Method/Prop | Description | Test | Status |
| ---------------------------------------------- | ------------------------------------------------------------------------------ | ---- | -------------- |
| static VERSION: string | The GUID/UUID version used. Always `4`. | OK | Ready |
| static VARIANTS: string[] | An array of valid VARIANT hexadecimal string values, `8`, `9`, `a`, and `b`. | OK | Ready |
| static validator: RegExp | A non-global Regular Expression that will validate a v4 GUID/UUID. | OK | Ready |
| static isGuidLike(maybeGuidLike: any): boolean | Checks if a string is hexadecimal in the 8-4-4-4-12 format | OK | Ready |
| static isGuid (maybeGuid: any): boolean | Check if value is a guid code | OK | Ready |
| static create ( ): Guid | Create a new guid | OK | Ready |
| static createEmpty ( ): Guid | Create an empty guid | OK | Ready |
| static parse (guid: string): Guid | Creates a guid instance from a given guid as string | OK | Ready |
| static raw ( ): string | Create a guid code in string format | OK | Ready |
| equals (other: Guid): boolean | Compare a guid code | OK | Ready |
| isEmpty ( ): boolean | Validate if a guid is empty | OK | Ready |
| toString ( ): string | Parse a guid instance to string format | OK | Ready |
| toObject ( ): any | Return the GUID as a JavaScript object | OK | Ready |
| toJSON ( ): any | Return the GUID as a JavaScript object. _Deprecated_ Use `toObject()` instead. | OK | **DEPRECATED** |
188 changes: 123 additions & 65 deletions lib/guid.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,128 @@
export class Guid {

public static validator = new RegExp("^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$", "i");

public static EMPTY = "00000000-0000-0000-0000-000000000000";

public static isGuid(guid: any) {
const value: string = guid.toString();
return guid && (guid instanceof Guid || Guid.validator.test(value));
}

public static create(): Guid {
return new Guid([Guid.gen(2), Guid.gen(1), Guid.gen(1), Guid.gen(1), Guid.gen(3)].join("-"));
}

public static createEmpty(): Guid {
return new Guid("emptyguid");
}

public static parse(guid: string): Guid {
return new Guid(guid);
}

public static raw(): string {
return [Guid.gen(2), Guid.gen(1), Guid.gen(1), Guid.gen(1), Guid.gen(3)].join("-");
}

private static gen(count: number) {
let out: string = "";
for (let i: number = 0; i < count; i++) {
// tslint:disable-next-line:no-bitwise
out += (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return out;
/**
* For random GUID/UUID values, the version field is 4
*/
public static readonly VERSION = '4';

/**
* For random GUID/UUID values, the variant field is one of these values.
*/
public static readonly VARIANTS = ['8', '9', 'a', 'b'];

/**
* This can be used to test if a string is "GUID-like", which is hexadecimal numbers in an 8-4-4-4-12 pattern.
*/
public static readonly guidLikeValidator = new RegExp("^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$", "i");

/**
* Checks for valid hexadecimal notation and ensures that version
* and variant are valid according to RFC 4122
* @see https://www.rfc-editor.org/rfc/rfc4122
*/
public static readonly v4Validator = new RegExp(
'^' +
[
'[a-z0-9]{8}',
'[a-z0-9]{4}',
`${Guid.VERSION}[a-z0-9]{3}`,
`[${Guid.VARIANTS.join('')}][a-z0-9]{3}`,
'[a-z0-9]{12}',
].join('-') +
'$',
'i'
);

public static EMPTY = '00000000-0000-0000-0000-000000000000';

public static isGuidLike(maybeGuidLike: any): boolean {
const stringValue = maybeGuidLike.toString();
return Guid.guidLikeValidator.test(stringValue);
}

public static isGuid(maybeGuid: any) {
const value: string = maybeGuid.toString();
// This short circuit is added because the validator won't validate the null GUID.
if (value === Guid.EMPTY) {
return true;
}

private value: string;

private constructor(guid: string) {
if (!guid) { throw new TypeError("Invalid argument; `value` has no value."); }

this.value = Guid.EMPTY;

if (guid && Guid.isGuid(guid)) {
this.value = guid;
}
return maybeGuid && (maybeGuid instanceof Guid || Guid.v4Validator.test(value));
}

public static create(): Guid {
return new Guid(Guid.raw());
}

public static createEmpty(): Guid {
return new Guid(null);
}

public static parse(guid: string): Guid {
return new Guid(guid);
}

/**
* Generates a raw GUID/UUID string.
* @returns Raw v4 GUID/UUID string
*/
public static raw(): string {
const version = Guid.gen(1).split('');
const variant = Guid.gen(1).split('');
/**
* First digit of the third nibble is always the version, 4
*/
version[0] = Guid.VERSION;

/**
* First digit of the fourth nibble is one of [8, 9, a, b]
*/
variant[0] = Guid.VARIANTS[Math.floor(Math.random() * Guid.VARIANTS.length)];
return [Guid.gen(2), Guid.gen(1), version.join(''), variant.join(''), Guid.gen(3)].join('-');
}

private static gen(count: number): string {
let out: string = '';
for (let i: number = 0; i < count; i++) {
// tslint:disable-next-line:no-bitwise
out += (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}

public equals(other: Guid): boolean {
// Comparing string `value` against provided `guid` will auto-call
// toString on `guid` for comparison
return Guid.isGuid(other) && this.value === other.toString();
}

public isEmpty(): boolean {
return this.value === Guid.EMPTY;
}

public toString(): string {
return this.value;
}

public toJSON(): any {
return {
value: this.value,
};
return out;
}

private value: string;

private constructor(guid: string | null) {
if (guid === Guid.EMPTY || guid === null) {
this.value = Guid.EMPTY;
return;
} else if (guid && Guid.isGuid(guid)) {
this.value = guid;
return;
}
throw new TypeError('Invalid argument; `guid` has no value.');
}

public equals(other: Guid): boolean {
// Comparing string `value` against provided `guid` will auto-call
// toString on `guid` for comparison
return Guid.isGuid(other) && this.value === other.toString();
}

public isEmpty(): boolean {
return this.value === Guid.EMPTY;
}

public toString(): string {
return this.value;
}

public toObject(): any {
return {
value: this.value,
};
}

public toJSON(): any {
console.warn('DEPRECATED: replace calls to guid.toJSON() with toObject()');
return this.toObject();
}
}


Loading