Skip to content

Commit

Permalink
Merge branch 'main' into feat-octal-validation
Browse files Browse the repository at this point in the history
  • Loading branch information
fabian-hiller committed Dec 23, 2023
2 parents 534e282 + e779e31 commit 9987d40
Show file tree
Hide file tree
Showing 25 changed files with 613 additions and 2 deletions.
4 changes: 4 additions & 0 deletions library/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to the library will be documented in this file.

## vX.X.X (Month DD, YYYY)

- Add `creditCard`, `hash`, `hex` and `hexColor` validation function (pull request #292, #304, #307, #308)

## v0.24.1 (December 11, 2023)

- Fix output type of optional `object` and `objectAsync` entries with default value (issue #286)
Expand Down
11 changes: 11 additions & 0 deletions library/src/regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ export const EMAIL_REGEX =
*/
export const EMOJI_REGEX = /^[\p{Extended_Pictographic}\p{Emoji_Component}]+$/u;

/**
* [Hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) regex.
*/
export const HEX_REGEX = /^(0h|0x)?[\da-f]+$/iu;

/**
* [Hex color](https://en.wikipedia.org/wiki/Web_colors#Hex_triplet) regex.
*/
export const HEX_COLOR_REGEX =
/^#([\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})$/iu;

/**
* [IMEI](https://en.wikipedia.org/wiki/International_Mobile_Equipment_Identity) regex.
*/
Expand Down
2 changes: 1 addition & 1 deletion library/src/utils/isLuhnAlgo/isLuhnAlgo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const NON_DIGIT_REGEX = /\D/gu;
*/
export function isLuhnAlgo(input: string) {
// Remove any non-digit chars
const number = input.replaceAll(NON_DIGIT_REGEX, '');
const number = input.replace(NON_DIGIT_REGEX, '');

// Create necessary variables
let length = number.length;
Expand Down
47 changes: 47 additions & 0 deletions library/src/validations/creditCard/creditCard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describe, expect, test } from 'vitest';
import { creditCard } from './creditCard.ts';

describe('creditCard', () => {
test('should pass valid cards', () => {
const validate = creditCard();

const value1 = '4539 1488 0343 6467';
expect(validate._parse(value1).output).toBe(value1);
const value2 = '4539 1488 0343 6467';
expect(validate._parse(value2).output).toBe(value2);
const value3 = '5555 5555 5555 4444';
expect(validate._parse(value3).output).toBe(value3);
const value4 = '3714 4963 5398 431';
expect(validate._parse(value4).output).toBe(value4);
const value5 = '3530 1113 3330 0000';
expect(validate._parse(value5).output).toBe(value5);
const value6 = '4701 3222 1111 1234';
expect(validate._parse(value6).output).toBe(value6);
const value7 = '4001 9192 5753 7193';
expect(validate._parse(value7).output).toBe(value7);
const value8 = '4005 5500 0000 0001';
expect(validate._parse(value8).output).toBe(value8);
const value9 = '3020 4169 3226 43';
expect(validate._parse(value9).output).toBe(value9);
const value10 = '3021 8047 1965 57';
expect(validate._parse(value10).output).toBe(value10);
const value11 = '30218047196557';
expect(validate._parse(value11).output).toBe(value11);

expect(validate._parse('').issues).toBeTruthy();
expect(validate._parse('1234 5678 9012 3456').issues).toBeTruthy();
expect(validate._parse('4532 7597 3454 2975 123').issues).toBeTruthy();
expect(validate._parse('4532-8940-1234-5678').issues).toBeTruthy();
expect(validate._parse('3543 0505 5555 5555').issues).toBeTruthy();
expect(validate._parse('55555 5555 5555 4444').issues).toBeTruthy();
expect(validate._parse('4111 1111 1111 111').issues).toBeTruthy();
expect(validate._parse('abcd efgh ijkl mnop').issues).toBeTruthy();
expect(validate._parse('0000 0000 0000 0000').issues).toBeTruthy();
});

test('should return custom error message', () => {
const error = 'Value is not a valid credit card number!';
const validate = creditCard(error);
expect(validate._parse('').issues?.[0].message).toBe(error);
});
});
74 changes: 74 additions & 0 deletions library/src/validations/creditCard/creditCard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { BaseValidation, ErrorMessage } from '../../types/index.ts';
import { actionIssue, actionOutput, isLuhnAlgo } from '../../utils/index.ts';

/**
* Credit card validation type.
*/
export type CreditCardValidation<TInput extends string> =
BaseValidation<TInput> & {
/**
* The validation type.
*/
type: 'credit_card';
/**
* The validation function.
*/
requirement: (input: string) => boolean;
};

/**
* Sanitize regex.
*/
const SANITIZE_REGEX = /[- ]+/gu;

/**
* Provider regex list.
*/
const PROVIDER_REGEX_LIST = [
// American Express
/^3[47]\d{13}$/u,
// Diners Club
/^3(?:0[0-5]|[68]\d)\d{11}$/u,
// Discover
/^6(?:011|5\d{2})\d{12,15}$/u,
// JCB
/^(?:2131|1800|35\d{3})\d{11}$/u,
// Mastercard
/^5[1-5]\d{2}|(222\d|22[3-9]\d|2[3-6]\d{2}|27[01]\d|2720)\d{12}$/u,
// UnionPay
/^(6[27]\d{14}|81\d{14,17})$/u,
// Visa
/^4\d{12}(?:\d{3,6})?$/u,
];

/**
* Creates a validation function that validates a [credit card](https://en.wikipedia.org/wiki/Payment_card_number).
*
* @param message The error message.
*
* @returns A validation function.
*/
export function creditCard<TInput extends string>(
message: ErrorMessage = 'Invalid credit card'
): CreditCardValidation<TInput> {
return {
type: 'credit_card',
async: false,
message,
requirement: (input) => {
// Remove any hyphens and blanks
const sanitized = input.replace(SANITIZE_REGEX, '');

// Check if it matches a provider and passes luhn algorithm
return (
PROVIDER_REGEX_LIST.some((regex) => regex.test(sanitized)) &&
isLuhnAlgo(sanitized)
);
},
_parse(input) {
return !this.requirement(input)
? actionIssue(this.type, this.message, input, this.requirement)
: actionOutput(input);
},
};
}
1 change: 1 addition & 0 deletions library/src/validations/creditCard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './creditCard.ts';
2 changes: 1 addition & 1 deletion library/src/validations/finite/finite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function finite<TInput extends number>(
async: false,
message,
requirement: Number.isFinite,
_parse(input: TInput) {
_parse(input) {
return !this.requirement(input)
? actionIssue(this.type, this.message, input, this.requirement)
: actionOutput(input);
Expand Down
172 changes: 172 additions & 0 deletions library/src/validations/hash/hash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { describe, expect, test } from 'vitest';
import { hash } from './hash.ts';

describe('hash', () => {
test('should pass only a valid hash string', () => {
const validate = hash(['md5']);
const value1 = 'd41d8cd98f00b204e9800998ecf8427e';
expect(validate._parse(value1).output).toBe(value1);

const validate2 = hash(['md4']);
const value2 = 'c93d3bf7a7c4afe94b64e30c2ce39f4f';
expect(validate2._parse(value2).output).toBe(value2);

const validate3 = hash(['sha1']);
const value3 = 'd033e22ae348aeb5660fc2140aec35850c4da997';
expect(validate3._parse(value3).output).toBe(value3);

const validate4 = hash(['sha256']);
const value4 =
'9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';
expect(validate4._parse(value4).output).toBe(value4);

const validate5 = hash(['sha384']);
const value5 =
'38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b';
expect(validate5._parse(value5).output).toBe(value5);

const validate6 = hash(['ripemd128']);
const value6 = 'c766f912a89d4ccda88e0cce6a713ef7';
expect(validate6._parse(value6).output).toBe(value6);

const validate7 = hash(['ripemd160']);
const value7 = '9c1185a5c5e9fc54612808977ee8f548b2258d31';
expect(validate7._parse(value7).output).toBe(value7);

const validate8 = hash(['tiger128']);
const value8 = '7ab383fc29d81f8d0d68e87c69bae5f1';
expect(validate8._parse(value8).output).toBe(value8);

const validate9 = hash(['tiger160']);
const value9 = '7ab383fc29d81f8d0d68e87c69bae5f1f18266d7';
expect(validate9._parse(value9).output).toBe(value9);

const validate10 = hash(['tiger192']);
const value10 = '4dd00f9e8e8a6a8e3883af1051237c4b47bd2a329b1de1a3';
expect(validate10._parse(value10).output).toBe(value10);

const validate11 = hash(['crc32']);
const value11 = '3d08bb77';
expect(validate11._parse(value11).output).toBe(value11);

const validate12 = hash(['crc32']);
const value12 = 'd87f7e0c';
expect(validate12._parse(value12).output).toBe(value12);

const validate13 = hash(['adler32']);
const value13 = '045d01c1';
expect(validate13._parse(value13).output).toBe(value13);

const validate14 = hash(['adler32', 'md5']);
const value14 = '045d01c1';
expect(validate14._parse(value14).output).toBe(value14);

const validate15 = hash(['md5', 'sha1', 'sha256']);
const value15 =
'9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';
expect(validate15._parse(value15).output).toBe(value15);
});

test('should reject invalid hash strings', () => {
const validate = hash(['md5']);

expect(validate._parse('12345').issues).toBeTruthy();
expect(
validate._parse('zxcvbnmasdfghjkqwertyuiop123456').issues
).toBeTruthy();
expect(
validate._parse('1234567890abcdef1234567890abcde').issues
).toBeTruthy();
expect(validate._parse('abcdef&$#').issues).toBeTruthy();

const validate2 = hash(['md4']);
expect(validate2._parse('12345').issues).toBeTruthy();
expect(
validate2._parse('zxcvbnmasdfghjkqwertyuiop123456').issues
).toBeTruthy();
expect(
validate2._parse('1234567890abcdef1234567890abcde').issues
).toBeTruthy();
expect(validate2._parse('abcdef&$#').issues).toBeTruthy();

const validate3 = hash(['sha1']);
expect(validate3._parse('1234567890abcdef12345').issues).toBeTruthy();
expect(
validate3._parse('1234567890abcdef1234567890abcdef1234xyz').issues
).toBeTruthy();
expect(
validate3._parse('1234567890abcdef1234567890abcdef123456789').issues
).toBeTruthy();
expect(validate3._parse('abcdef&$#').issues).toBeTruthy();

const validate4 = hash(['sha256']);
expect(
validate4._parse(
'abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456789'
).issues
).toBeTruthy();
expect(
validate4._parse(
'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefg'
).issues
).toBeTruthy();
expect(
validate4._parse(
'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890xyz'
).issues
).toBeTruthy();
expect(validate4._parse('abcdef&$#').issues).toBeTruthy();
expect(validate4._parse('abcdef&$#').issues).toBeTruthy();

const validate5 = hash(['sha384']);
expect(validate5._parse('abcd').issues).toBeTruthy();
expect(
validate5._parse(
'abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd'
).issues
).toBeTruthy();
expect(
validate5._parse(
'abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd123'
).issues
).toBeTruthy();
expect(validate5._parse('abcdef&$#').issues).toBeTruthy();

const validate6 = hash(['sha512']);
expect(validate6._parse('abcdef').issues).toBeTruthy();
expect(
validate6._parse(
'01de2f2b94f2032e91ecc7bc6f2d5e9c6b74db6ff7b5c56eabfa3b38421a485c3f5f194a5f84e3ac7dc2b3ad4f3c3d8770444010a2336c5e0e16e7a4d58a8f02a'
).issues
).toBeTruthy();
expect(
validate6._parse(
'01de2f2b94f2032e91ecc7bc6f2d5e9c6b74db6ff7b5c56eabfa3b38421a485c3f5f194a5f84e3ac7dc2b3ad4f3c3d8770444010a2336c5e0e16e7a4d58a8f02g'
).issues
).toBeTruthy();
expect(validate6._parse('abcdef&$#').issues).toBeTruthy();

const validate7 = hash(['crc32']);
expect(validate7._parse('12345').issues).toBeTruthy();
expect(validate7._parse('3df4b6729').issues).toBeTruthy();
expect(validate7._parse('3df4b67z').issues).toBeTruthy();
expect(validate7._parse('3df4b67%').issues).toBeTruthy();
expect(validate7._parse('abcdef&$#').issues).toBeTruthy();

const validate8 = hash(['adler32']);
expect(validate8._parse('12345').issues).toBeTruthy();
expect(validate8._parse('3df4b6729').issues).toBeTruthy();
expect(validate8._parse('3df4b67z').issues).toBeTruthy();
expect(validate8._parse('3df4b67z%$').issues).toBeTruthy();
expect(validate8._parse('abcdef&$#').issues).toBeTruthy();

const validate9 = hash(['md5', 'sha1', 'sha256']);
expect(validate9._parse('12345').issues).toBeTruthy();
});

test('should return custom error message', () => {
const error = 'Value is not a sha1 hash!';
const validate = hash(['sha1'], error);
expect(validate._parse('sdsds').issues?.[0].message).toBe(error);
});
});
Loading

0 comments on commit 9987d40

Please sign in to comment.