Skip to content

Commit

Permalink
Merge pull request #219 from fabian-hiller/feat/export-regex
Browse files Browse the repository at this point in the history
Add export for any validation regex
  • Loading branch information
fabian-hiller authored Oct 21, 2023
2 parents 0b5762f + 236d6c1 commit a43ac42
Show file tree
Hide file tree
Showing 19 changed files with 117 additions and 32 deletions.
1 change: 1 addition & 0 deletions library/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to the library will be documented in this file.
- Add `getRestAndDefaultArgs` utility function
- Add new `rest` argument to `object` and `objectAsync` schema
- Add `PartialObjectEntries` and `PartialObjectEntriesAsync` type (issue #217)
- Add export for any validation regex (pull request #219)
- Fix type check in `date` and `dateAsync` for invalid dates (pull request #214)
- Improve security of regular expressions (pull request #202)
- Change `ObjectSchema` and `ObjectSchemaAsync` type
Expand Down
1 change: 1 addition & 0 deletions library/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './error/index.ts';
export * from './methods/index.ts';
export * from './regex.ts';
export * from './schemas/index.ts';
export * from './transformations/index.ts';
export * from './utils/index.ts';
Expand Down
75 changes: 75 additions & 0 deletions library/src/regex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* [cuid2](https://github.com/paralleldrive/cuid2#cuid2) regex.
*/
export const CUID2_REGEX = /^[a-z][\da-z]*$/u;

/**
* Email regex.
*/
export const EMAIL_REGEX =
/^[\w+-]+(?:\.[\w+-]+)*@[\da-z]+(?:[.-][\da-z]+)*\.[a-z]{2,}$/iu;

/**
* Emoji regex.
*/
export const EMOJI_REGEX = /^[\p{Extended_Pictographic}\p{Emoji_Component}]+$/u;

/**
* [IMEI](https://en.wikipedia.org/wiki/International_Mobile_Equipment_Identity) regex.
*/
export const IMEI_REGEX = /^\d{2}(?:[ /|-]?\d{6}){2}[ /|-]?\d$/u;

/**
* [IPv4](https://en.wikipedia.org/wiki/IPv4) regex.
*/
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive
export const IPV4_REGEX = /^(?:(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)\.?\b){4}$/u;

/**
* [IPv6](https://en.wikipedia.org/wiki/IPv6) regex.
*/
export const IPV6_REGEX =
/^(?:(?:[\da-f]{1,4}:){7}[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,7}:|(?:[\da-f]{1,4}:){1,6}:[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,5}(?::[\da-f]{1,4}){1,2}|(?:[\da-f]{1,4}:){1,4}(?::[\da-f]{1,4}){1,3}|(?:[\da-f]{1,4}:){1,3}(?::[\da-f]{1,4}){1,4}|(?:[\da-f]{1,4}:){1,2}(?::[\da-f]{1,4}){1,5}|[\da-f]{1,4}:(?::[\da-f]{1,4}){1,6}|:(?:(?::[\da-f]{1,4}){1,7}|:)|fe80:(?::[\da-f]{0,4}){0,4}%[\da-z]+|::(?:f{4}(?::0{1,4})?:)?(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)|(?:[\da-f]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d))$/iu;

/**
* [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date regex.
*/
export const ISO_DATE_REGEX =
/^\d{4}-(?:0[1-9]|1[0-2])-(?:[12]\d|0[1-9]|3[01])$/u;

/**
* [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date-time regex.
*/
export const ISO_DATE_TIME_REGEX =
/^\d{4}-(?:0[1-9]|1[0-2])-(?:[12]\d|0[1-9]|3[01])T(?:0\d|1\d|2[0-3]):[0-5]\d$/u;

/**
* [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) time regex.
*/
export const ISO_TIME_REGEX = /^(?:0\d|1\d|2[0-3]):[0-5]\d$/u;

/**
* [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) time with seconds regex.
*/
export const ISO_TIME_SECOND_REGEX = /^(?:0\d|1\d|2[0-3])(?::[0-5]\d){2}$/u;

/**
* [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) timestamp regex.
*/
export const ISO_TIMESTAMP_REGEX =
/^\d{4}-(?:0[1-9]|1[0-2])-(?:[12]\d|0[1-9]|3[01])T(?:0\d|1\d|2[0-3])(?::[0-5]\d){2}\.\d{3}Z$/u;

/**
* [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) week regex.
*/
export const ISO_WEEK_REGEX = /^\d{4}-W(?:0[1-9]|[1-4]\d|5[0-3])$/u;

/**
* [ULID](https://github.com/ulid/spec) regex.
*/
export const ULID_REGEX = /^[\da-hjkmnp-tv-z]{26}$/iu;

/**
* [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) regex.
*/
export const UUID_REGEX = /^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/iu;
7 changes: 6 additions & 1 deletion library/src/utils/isLuhnAlgo/isLuhnAlgo.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* Non-digit regex.
*/
const NON_DIGIT_REGEX = /\D/u;

/**
* Checks whether a string with numbers corresponds to the luhn algorithm.
*
Expand All @@ -7,7 +12,7 @@
*/
export function isLuhnAlgo(input: string) {
// Remove any non-digit chars
const number = input.replace(/\D/gu, '');
const number = input.replaceAll(NON_DIGIT_REGEX, '');

// Create necessary variables
let length = number.length;
Expand Down
3 changes: 2 additions & 1 deletion library/src/validations/cuid2/cuid2.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CUID2_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -10,7 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function cuid2<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^[a-z][\da-z]*$/u.test(input)
!CUID2_REGEX.test(input)
? getPipeIssues('cuid2', error || 'Invalid cuid2', input)
: getOutput(input);
}
5 changes: 2 additions & 3 deletions library/src/validations/email/email.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EMAIL_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -10,9 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function email<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^[\w+-]+(?:\.[\w+-]+)*@[\da-z]+(?:[.-][\da-z]+)*\.[a-z]{2,}$/iu.test(
input
)
!EMAIL_REGEX.test(input)
? getPipeIssues('email', error || 'Invalid email', input)
: getOutput(input);
}
3 changes: 2 additions & 1 deletion library/src/validations/emoji/emoji.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EMOJI_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -10,7 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function emoji<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^[\p{Extended_Pictographic}\p{Emoji_Component}]+$/u.test(input)
!EMOJI_REGEX.test(input)
? getPipeIssues('emoji', error || 'Invalid emoji', input)
: getOutput(input);
}
5 changes: 3 additions & 2 deletions library/src/validations/imei/imei.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IMEI_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues, isLuhnAlgo } from '../../utils/index.ts';

/**
* Creates a validation function that validates an IMEI.
* Creates a validation function that validates an [IMEI](https://en.wikipedia.org/wiki/International_Mobile_Equipment_Identity).
*
* Format: AA-BBBBBB-CCCCCC-D
*
Expand All @@ -12,7 +13,7 @@ import { getOutput, getPipeIssues, isLuhnAlgo } from '../../utils/index.ts';
*/
export function imei<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^\d{2}(?:[ /|-]?\d{6}){2}[ /|-]?\d$/u.test(input) || !isLuhnAlgo(input)
!IMEI_REGEX.test(input) || !isLuhnAlgo(input)
? getPipeIssues('imei', error || 'Invalid IMEI', input)
: getOutput(input);
}
10 changes: 4 additions & 6 deletions library/src/validations/ip/ip.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import { IPV4_REGEX, IPV6_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

/**
* Creates a validation function that validates an IP v4 or v6 address.
* Creates a validation function that validates an [IPv4](https://en.wikipedia.org/wiki/IPv4)
* or [IPv6](https://en.wikipedia.org/wiki/IPv6) address.
*
* @param error The error message.
*
* @returns A validation function.
*/
export function ip<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive
!/^(?:(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)\.?\b){4}$/u.test(input) &&
!/^(?:(?:[\da-f]{1,4}:){7}[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,7}:|(?:[\da-f]{1,4}:){1,6}:[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,5}(?::[\da-f]{1,4}){1,2}|(?:[\da-f]{1,4}:){1,4}(?::[\da-f]{1,4}){1,3}|(?:[\da-f]{1,4}:){1,3}(?::[\da-f]{1,4}){1,4}|(?:[\da-f]{1,4}:){1,2}(?::[\da-f]{1,4}){1,5}|[\da-f]{1,4}:(?::[\da-f]{1,4}){1,6}|:(?:(?::[\da-f]{1,4}){1,7}|:)|fe80:(?::[\da-f]{0,4}){0,4}%[\da-z]+|::(?:f{4}(?::0{1,4})?:)?(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)|(?:[\da-f]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d))$/iu.test(
input
)
!IPV4_REGEX.test(input) && !IPV6_REGEX.test(input)
? getPipeIssues('ip', error || 'Invalid IP', input)
: getOutput(input);
}
4 changes: 2 additions & 2 deletions library/src/validations/ipv4/ipv4.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IPV4_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -10,8 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function ipv4<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive
!/^(?:(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)\.?\b){4}$/u.test(input)
!IPV4_REGEX.test(input)
? getPipeIssues('ipv4', error || 'Invalid IP v4', input)
: getOutput(input);
}
5 changes: 2 additions & 3 deletions library/src/validations/ipv6/ipv6.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { IPV6_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -10,9 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function ipv6<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^(?:(?:[\da-f]{1,4}:){7}[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,7}:|(?:[\da-f]{1,4}:){1,6}:[\da-f]{1,4}|(?:[\da-f]{1,4}:){1,5}(?::[\da-f]{1,4}){1,2}|(?:[\da-f]{1,4}:){1,4}(?::[\da-f]{1,4}){1,3}|(?:[\da-f]{1,4}:){1,3}(?::[\da-f]{1,4}){1,4}|(?:[\da-f]{1,4}:){1,2}(?::[\da-f]{1,4}){1,5}|[\da-f]{1,4}:(?::[\da-f]{1,4}){1,6}|:(?:(?::[\da-f]{1,4}){1,7}|:)|fe80:(?::[\da-f]{0,4}){0,4}%[\da-z]+|::(?:f{4}(?::0{1,4})?:)?(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)|(?:[\da-f]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d))$/iu.test(
input
)
!IPV6_REGEX.test(input)
? getPipeIssues('ipv6', error || 'Invalid IP v6', input)
: getOutput(input);
}
3 changes: 2 additions & 1 deletion library/src/validations/isoDate/isoDate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISO_DATE_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -16,7 +17,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function isoDate<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^\d{4}-(?:0[1-9]|1[0-2])-(?:[12]\d|0[1-9]|3[01])$/u.test(input)
!ISO_DATE_REGEX.test(input)
? getPipeIssues('iso_date', error || 'Invalid date', input)
: getOutput(input);
}
5 changes: 2 additions & 3 deletions library/src/validations/isoDateTime/isoDateTime.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISO_DATE_TIME_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -16,9 +17,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function isoDateTime<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^\d{4}-(?:0[1-9]|1[0-2])-(?:[12]\d|0[1-9]|3[01])T(?:0\d|1\d|2[0-3]):[0-5]\d$/u.test(
input
)
!ISO_DATE_TIME_REGEX.test(input)
? getPipeIssues('iso_date_time', error || 'Invalid datetime', input)
: getOutput(input);
}
3 changes: 2 additions & 1 deletion library/src/validations/isoTime/isoTime.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISO_TIME_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -12,7 +13,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function isoTime<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^(?:0\d|1\d|2[0-3]):[0-5]\d$/u.test(input)
!ISO_TIME_REGEX.test(input)
? getPipeIssues('iso_time', error || 'Invalid time', input)
: getOutput(input);
}
3 changes: 2 additions & 1 deletion library/src/validations/isoTimeSecond/isoTimeSecond.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISO_TIME_SECOND_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -12,7 +13,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function isoTimeSecond<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^(?:0\d|1\d|2[0-3])(?::[0-5]\d){2}$/u.test(input)
!ISO_TIME_SECOND_REGEX.test(input)
? getPipeIssues('iso_time_second', error || 'Invalid time', input)
: getOutput(input);
}
5 changes: 2 additions & 3 deletions library/src/validations/isoTimestamp/isoTimestamp.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISO_TIMESTAMP_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -16,9 +17,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function isoTimestamp<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^\d{4}-(?:0[1-9]|1[0-2])-(?:[12]\d|0[1-9]|3[01])T(?:0\d|1\d|2[0-3])(?::[0-5]\d){2}\.\d{3}Z$/u.test(
input
)
!ISO_TIMESTAMP_REGEX.test(input)
? getPipeIssues('iso_timestamp', error || 'Invalid timestamp', input)
: getOutput(input);
}
3 changes: 2 additions & 1 deletion library/src/validations/isoWeek/isoWeek.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ISO_WEEK_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -16,7 +17,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function isoWeek<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^\d{4}-W(?:0[1-9]|[1-4]\d|5[0-3])$/u.test(input)
!ISO_WEEK_REGEX.test(input)
? getPipeIssues('iso_week', error || 'Invalid week', input)
: getOutput(input);
}
3 changes: 2 additions & 1 deletion library/src/validations/ulid/ulid.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ULID_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

Expand All @@ -10,7 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts';
*/
export function ulid<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^[\da-hjkmnp-tv-z]{26}$/iu.test(input)
!ULID_REGEX.test(input)
? getPipeIssues('ulid', error || 'Invalid ULID', input)
: getOutput(input);
}
5 changes: 3 additions & 2 deletions library/src/validations/uuid/uuid.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { UUID_REGEX } from '../../regex.ts';
import type { ErrorMessage, PipeResult } from '../../types.ts';
import { getOutput, getPipeIssues } from '../../utils/index.ts';

/**
* Creates a validation function that validates a UUID.
* Creates a validation function that validates a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier).
*
* @param error The error message.
*
* @returns A validation function.
*/
export function uuid<TInput extends string>(error?: ErrorMessage) {
return (input: TInput): PipeResult<TInput> =>
!/^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/iu.test(input)
!UUID_REGEX.test(input)
? getPipeIssues('uuid', error || 'Invalid UUID', input)
: getOutput(input);
}

0 comments on commit a43ac42

Please sign in to comment.