Skip to content

Commit

Permalink
feat: validate (#75)
Browse files Browse the repository at this point in the history
* feat: Add validate method to translators
* test: Ensure 100% coverage
* docs: Update JSDoc, types, README, CHANGELOG
* 5.2.0
  • Loading branch information
oculus42 authored May 2, 2024
1 parent 47ea79a commit 60e74f5
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 31 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [5.2.0] - 2024-05-01
### Added
- `validate` method to check shortIds for simple or "complete" validity

## [5.1.0] - 2024-04-30
### Added
- Added `uuid25Base36` constant to support uuid25 style
- `uuid25Base36` constant to support uuid25 style

## [5.0.1] - 2024-04-30
### Changed
Expand Down
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

Generate and translate standard UUIDs into shorter - or just *different* - formats and back.

## v5.1.0
## v5.2.0
5.2.0 adds `validate` method to check short IDs. Requested by [@U-4-E-A](https://github.com/U-4-E-A)
5.1.0 adds translation support for the [uuid25](https://github.com/uuid25/javascript) (Base36) format
with the `uuid25Base36` constant.

Expand All @@ -19,14 +20,15 @@ with the `uuid25Base36` constant.
const short = require('short-uuid');

// Quick start with flickrBase58 format
short.generate(); // 73WakrfVbNJBaAmhQtEeDv
short.generate(); // '73WakrfVbNJBaAmhQtEeDv'
```

### Details

short-uuid starts with RFC4122 v4-compliant UUIDs and translates them
into other, usually shorter formats. It also provides translators
to convert back and forth from RFC compliant UUIDs to the shorter formats.
to convert back and forth from RFC compliant UUIDs to the shorter formats,
and validate the IDs.

As of 4.0.0, formats return consistent-length values unless specifically requested.
This is done by padding the start with the first (`[0]`) character in the alphabet.
Expand All @@ -35,6 +37,9 @@ Previous versions can translate padded formats back to UUID.
```javascript
const short = require('short-uuid');

// Generate a flickrBase58 short ID from without creating a translator
const shortId = short.generate();

const translator = short(); // Defaults to flickrBase58
const decimalTranslator = short("0123456789"); // Provide a specific alphabet for translation
const cookieTranslator = short(short.constants.cookieBase90); // Use a constant for translation
Expand All @@ -47,11 +52,12 @@ translator.generate(); // An alias for new.
translator.toUUID(shortId); // a44521d0-0fb8-4ade-8002-3385545c3318
translator.fromUUID(regularUUID); // mhvXdrZT4jP5T8vBxuvm75

// Generate plain UUIDs
// - From the library without creating a translator
short.uuid(); // fd5c084c-ff7c-4651-9a52-37096242d81c
// - Each translator provides the uuid.v4() function, too
translator.uuid(); // 3023b0f5-ec55-4e75-9cd8-104700698052
// Check if a string is a valid ID (length and alphabet)
translator.validate(shortId); // true

// Check if a string is valid *AND* translates to a valid UUID
translator.validate(shortId, true); // true
translator.validate('0000000000000000000000', true) // false

// See the alphabet used by a translator
translator.alphabet;
Expand All @@ -64,6 +70,12 @@ translator.maxLength;
short.constants.cookieBase90; // Safe for HTTP cookies values for smaller IDs.
short.constants.flickrBase58; // Avoids similar characters (0/O, 1/I/l, etc.)
short.constants.uuid25Base36; // The uuid25 (string length 25) format

// Generate plain UUIDs
// - From the library without creating a translator
short.uuid(); // fd5c084c-ff7c-4651-9a52-37096242d81c
// - Each translator provides the uuid.v4() function, too
translator.uuid(); // 3023b0f5-ec55-4e75-9cd8-104700698052
```

### Options
Expand All @@ -87,7 +99,7 @@ translator.new(); // mhvXdrZT4jP5T8vBxuvm75

## Support

short-uuid [5.x](https://github.com/oculus42/short-uuid/blob/v5.1.0/README.md)
short-uuid [5.x](https://github.com/oculus42/short-uuid/blob/v5.2.0/README.md)
and later is tested on Node 14.x and later.

short-uuid [4.x](https://github.com/oculus42/short-uuid/blob/v3.2.2/README.md)
Expand Down
3 changes: 3 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ declare module 'short-uuid' {

/** long -> short */
fromUUID(regularUUID: string | UUID): SUUID;

/** validate short */
validate(shortId: string | SUUID, rigorous?: boolean): boolean;
}
}

Expand Down
61 changes: 45 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Simple wrapper functions to produce shorter UUIDs for cookies, maybe everything?
*/

const { v4: uuidV4 } = require('uuid');
const { v4: uuidV4, validate: uuidValidate } = require('uuid');
const anyBase = require('any-base');

const constants = {
Expand All @@ -22,7 +22,7 @@ let toFlickr;
/**
* Takes a UUID, strips the dashes, and translates.
* @param {string} longId
* @param {function(string)} translator
* @param {function(string):string} translator
* @param {Object} [paddingParams]
* @returns {string}
*/
Expand Down Expand Up @@ -53,20 +53,27 @@ const enlargeUUID = (shortId, translator) => {
return [m[1], m[2], m[3], m[4], m[5]].join('-');
};

// Calculate length for the shortened ID
/**
* Calculate length for the shortened ID
* @param {number} alphabetLength
* @returns {number}
*/
const getShortIdLength = (alphabetLength) => (
Math.ceil(Math.log(2 ** 128) / Math.log(alphabetLength)));

module.exports = (() => {
/**
* @param {string} toAlphabet - Defaults to flickrBase58 if not provided
* @param {Object} [options]
*
* @returns {{new: (function()),
* uuid: (function()),
* fromUUID: (function(string)),
* toUUID: (function(string)),
* alphabet: (string)}}
* @param {string} toAlphabet
* @param {{ consistentLength: boolean }} [options]
* @returns {{
* alphabet: string,
* fromUUID: (function(*): string),
* generate: (function(): string),
* maxLength: number,
* new: (function(): string),
* toUUID: (function(*): string),
* uuid: ((function(*, *, *): (*))|*),
* validate: ((function(*, boolean=false): (boolean))|*)}}
*/
const makeConvertor = (toAlphabet, options) => {
// Default to Flickr 58
Expand All @@ -92,16 +99,38 @@ module.exports = (() => {
// UUIDs are in hex, so we translate to and from.
const fromHex = anyBase(anyBase.HEX, useAlphabet);
const toHex = anyBase(useAlphabet, anyBase.HEX);
/**
* @returns {string} - short id
*/
const generate = () => shortenUUID(uuidV4(), fromHex, paddingParams);

/**
* Confirm if string is a valid id. Checks length and alphabet.
* If the second parameter is true it will translate to standard UUID
* and check the result for UUID validity.
* @param {string} shortId - The string to check for validity
* @param {boolean} [rigorous=false] - If true, also check for a valid UUID
* @returns {boolean}
*/
const validate = (shortId, rigorous = false) => {
if (!shortId || typeof shortId !== 'string') return false;
const isCorrectLength = selectedOptions.consistentLength
? shortId.length === shortIdLength
: shortId.length <= shortIdLength;
const onlyAlphabet = shortId.split('').every((letter) => useAlphabet.includes(letter));
if (rigorous === false) return isCorrectLength && onlyAlphabet;
return isCorrectLength && onlyAlphabet && uuidValidate(enlargeUUID(shortId, toHex));
};

const translator = {
new: generate,
generate,
uuid: uuidV4,
fromUUID: (uuid) => shortenUUID(uuid, fromHex, paddingParams),
toUUID: (shortUuid) => enlargeUUID(shortUuid, toHex),
alphabet: useAlphabet,
fromUUID: (uuid) => shortenUUID(uuid, fromHex, paddingParams),
maxLength: shortIdLength,
generate,
new: generate,
toUUID: (shortUuid) => enlargeUUID(shortUuid, toHex),
uuid: uuidV4,
validate,
};

Object.freeze(translator);
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "short-uuid",
"version": "5.1.0",
"version": "5.2.0",
"description": "Create and translate standard UUIDs with shorter formats.",
"main": "index.js",
"typings": "index.d.ts",
Expand Down
57 changes: 54 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,11 @@ test('uuid25 should be compatible with uuid25 examples', (t) => {
});
});

test('uuid25 translator should provide maxLength 25', (t) => {
t.plan(1);
t.equal(b36.maxLength, 25);
test('Translator should provide correct maxLength', (t) => {
t.plan(3);
t.equal(b36.maxLength, 25, 'uuid25 is 25');
t.equal(b58.maxLength, 22, 'flickr is 22');
t.equal(b90.maxLength, 20, 'cookie is 20');
});

test('Default generate quantity tests', (t) => {
Expand All @@ -286,3 +288,52 @@ test('Default generate quantity tests', (t) => {

t.equal(underLength, 0, 'Ensure default is padded');
});

test('Validate', (t) => {
t.plan(25);

// Bad type
t.notOk(b36.validate(100), 'only strings');

// Too short
t.notOk(b36.validate('123'), 'uuid25 too short');
t.notOk(b58.validate('123'), 'flickr too short');
t.notOk(b90.validate('123'), 'cookie too short');

// Short is valid without consistentLength
const s36 = short(short.constants.uuid25Base36, { consistentLength: false });
const s58 = short(short.constants.flickrBase58, { consistentLength: false });
const s90 = short(short.constants.cookieBase90, { consistentLength: false });
t.ok(s36.validate('111'));
t.ok(s58.validate('111'));
t.ok(s90.validate('111'));

// Too long
t.notOk(b36.validate('123456789012345678901234567890'), 'uuid25 too long');
t.notOk(b58.validate('123456789012345678901234567890'), 'flickr too long');
t.notOk(b90.validate('123456789012345678901234567890'), 'cookie too long');

// Bad alphabet
t.notOk(b36.validate('123456789012345678901234"'), 'uuid25 validates alphabet');
t.notOk(b58.validate('123456789012345678901"'), 'flickr validates alphabet');
t.notOk(b90.validate('1234567890123456789"'), 'cookie validates alphabet');

// Generated value passes
t.ok(b36.validate(b36.generate()), 'uuid25 validates');
t.ok(b58.validate(b58.generate()), 'flickr validates');
t.ok(b90.validate(b90.generate()), 'cookie validates');

// "empty" value passes without uuid check
t.notOk(b36.validate('0'), 'uuid25 passes bad uuid without check');
t.notOk(b58.validate('0'), 'flickr fails bad uuid without check');
t.notOk(b90.validate('0'), 'cookie fails bad uuid without check');

// With uuid check
t.ok(b36.validate(b36.generate(), true), 'uuid25 validates uuid');
t.ok(b58.validate(b58.generate(), true), 'flickr validates uuid');
t.ok(b90.validate(b90.generate(), true), 'cookie validates uuid');

t.notOk(b36.validate('0', true), 'uuid25 fails bad uuid');
t.notOk(b58.validate('0', true), 'flickr fails bad uuid');
t.notOk(b90.validate('0', true), 'cookie fails bad uuid');
});

0 comments on commit 60e74f5

Please sign in to comment.