diff --git a/library/CHANGELOG.md b/library/CHANGELOG.md index b1af87a80..d948c4582 100644 --- a/library/CHANGELOG.md +++ b/library/CHANGELOG.md @@ -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 diff --git a/library/src/index.ts b/library/src/index.ts index 8d61d3b24..7fbe22168 100644 --- a/library/src/index.ts +++ b/library/src/index.ts @@ -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'; diff --git a/library/src/regex.ts b/library/src/regex.ts new file mode 100644 index 000000000..f8a518041 --- /dev/null +++ b/library/src/regex.ts @@ -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; diff --git a/library/src/utils/isLuhnAlgo/isLuhnAlgo.ts b/library/src/utils/isLuhnAlgo/isLuhnAlgo.ts index 520a17abc..e5c2e1b31 100644 --- a/library/src/utils/isLuhnAlgo/isLuhnAlgo.ts +++ b/library/src/utils/isLuhnAlgo/isLuhnAlgo.ts @@ -1,3 +1,8 @@ +/** + * Non-digit regex. + */ +const NON_DIGIT_REGEX = /\D/u; + /** * Checks whether a string with numbers corresponds to the luhn algorithm. * @@ -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; diff --git a/library/src/validations/cuid2/cuid2.ts b/library/src/validations/cuid2/cuid2.ts index e364fba5d..781fcc28a 100644 --- a/library/src/validations/cuid2/cuid2.ts +++ b/library/src/validations/cuid2/cuid2.ts @@ -1,3 +1,4 @@ +import { CUID2_REGEX } from '../../regex.ts'; import type { ErrorMessage, PipeResult } from '../../types.ts'; import { getOutput, getPipeIssues } from '../../utils/index.ts'; @@ -10,7 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function cuid2(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^[a-z][\da-z]*$/u.test(input) + !CUID2_REGEX.test(input) ? getPipeIssues('cuid2', error || 'Invalid cuid2', input) : getOutput(input); } diff --git a/library/src/validations/email/email.ts b/library/src/validations/email/email.ts index f57ea8a30..3ed274a38 100644 --- a/library/src/validations/email/email.ts +++ b/library/src/validations/email/email.ts @@ -1,3 +1,4 @@ +import { EMAIL_REGEX } from '../../regex.ts'; import type { ErrorMessage, PipeResult } from '../../types.ts'; import { getOutput, getPipeIssues } from '../../utils/index.ts'; @@ -10,9 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function email(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^[\w+-]+(?:\.[\w+-]+)*@[\da-z]+(?:[.-][\da-z]+)*\.[a-z]{2,}$/iu.test( - input - ) + !EMAIL_REGEX.test(input) ? getPipeIssues('email', error || 'Invalid email', input) : getOutput(input); } diff --git a/library/src/validations/emoji/emoji.ts b/library/src/validations/emoji/emoji.ts index de8a8b120..cda84377f 100644 --- a/library/src/validations/emoji/emoji.ts +++ b/library/src/validations/emoji/emoji.ts @@ -1,3 +1,4 @@ +import { EMOJI_REGEX } from '../../regex.ts'; import type { ErrorMessage, PipeResult } from '../../types.ts'; import { getOutput, getPipeIssues } from '../../utils/index.ts'; @@ -10,7 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function emoji(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^[\p{Extended_Pictographic}\p{Emoji_Component}]+$/u.test(input) + !EMOJI_REGEX.test(input) ? getPipeIssues('emoji', error || 'Invalid emoji', input) : getOutput(input); } diff --git a/library/src/validations/imei/imei.ts b/library/src/validations/imei/imei.ts index 896beb67f..4fa885ae2 100644 --- a/library/src/validations/imei/imei.ts +++ b/library/src/validations/imei/imei.ts @@ -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 * @@ -12,7 +13,7 @@ import { getOutput, getPipeIssues, isLuhnAlgo } from '../../utils/index.ts'; */ export function imei(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^\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); } diff --git a/library/src/validations/ip/ip.ts b/library/src/validations/ip/ip.ts index 0297fb1e1..d8ca5cbf5 100644 --- a/library/src/validations/ip/ip.ts +++ b/library/src/validations/ip/ip.ts @@ -1,8 +1,10 @@ +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. * @@ -10,11 +12,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function ip(error?: ErrorMessage) { return (input: TInput): PipeResult => - // 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); } diff --git a/library/src/validations/ipv4/ipv4.ts b/library/src/validations/ipv4/ipv4.ts index 4e0b46fbe..85fd5e035 100644 --- a/library/src/validations/ipv4/ipv4.ts +++ b/library/src/validations/ipv4/ipv4.ts @@ -1,3 +1,4 @@ +import { IPV4_REGEX } from '../../regex.ts'; import type { ErrorMessage, PipeResult } from '../../types.ts'; import { getOutput, getPipeIssues } from '../../utils/index.ts'; @@ -10,8 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function ipv4(error?: ErrorMessage) { return (input: TInput): PipeResult => - // 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); } diff --git a/library/src/validations/ipv6/ipv6.ts b/library/src/validations/ipv6/ipv6.ts index d8489d197..3d24c2e3c 100644 --- a/library/src/validations/ipv6/ipv6.ts +++ b/library/src/validations/ipv6/ipv6.ts @@ -1,3 +1,4 @@ +import { IPV6_REGEX } from '../../regex.ts'; import type { ErrorMessage, PipeResult } from '../../types.ts'; import { getOutput, getPipeIssues } from '../../utils/index.ts'; @@ -10,9 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function ipv6(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^(?:(?:[\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); } diff --git a/library/src/validations/isoDate/isoDate.ts b/library/src/validations/isoDate/isoDate.ts index 9e3e918c0..e4bf74854 100644 --- a/library/src/validations/isoDate/isoDate.ts +++ b/library/src/validations/isoDate/isoDate.ts @@ -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'; @@ -16,7 +17,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function isoDate(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^\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); } diff --git a/library/src/validations/isoDateTime/isoDateTime.ts b/library/src/validations/isoDateTime/isoDateTime.ts index 4a856cb99..fe5043536 100644 --- a/library/src/validations/isoDateTime/isoDateTime.ts +++ b/library/src/validations/isoDateTime/isoDateTime.ts @@ -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'; @@ -16,9 +17,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function isoDateTime(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^\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); } diff --git a/library/src/validations/isoTime/isoTime.ts b/library/src/validations/isoTime/isoTime.ts index 587179cb5..82d35a143 100644 --- a/library/src/validations/isoTime/isoTime.ts +++ b/library/src/validations/isoTime/isoTime.ts @@ -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'; @@ -12,7 +13,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function isoTime(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^(?: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); } diff --git a/library/src/validations/isoTimeSecond/isoTimeSecond.ts b/library/src/validations/isoTimeSecond/isoTimeSecond.ts index 06ae583a9..695582118 100644 --- a/library/src/validations/isoTimeSecond/isoTimeSecond.ts +++ b/library/src/validations/isoTimeSecond/isoTimeSecond.ts @@ -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'; @@ -12,7 +13,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function isoTimeSecond(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^(?: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); } diff --git a/library/src/validations/isoTimestamp/isoTimestamp.ts b/library/src/validations/isoTimestamp/isoTimestamp.ts index 3233c2d96..3dd80c554 100644 --- a/library/src/validations/isoTimestamp/isoTimestamp.ts +++ b/library/src/validations/isoTimestamp/isoTimestamp.ts @@ -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'; @@ -16,9 +17,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function isoTimestamp(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^\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); } diff --git a/library/src/validations/isoWeek/isoWeek.ts b/library/src/validations/isoWeek/isoWeek.ts index bb2142e9e..5c0b6adf5 100644 --- a/library/src/validations/isoWeek/isoWeek.ts +++ b/library/src/validations/isoWeek/isoWeek.ts @@ -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'; @@ -16,7 +17,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function isoWeek(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^\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); } diff --git a/library/src/validations/ulid/ulid.ts b/library/src/validations/ulid/ulid.ts index c7ae0a009..ef91cca56 100644 --- a/library/src/validations/ulid/ulid.ts +++ b/library/src/validations/ulid/ulid.ts @@ -1,3 +1,4 @@ +import { ULID_REGEX } from '../../regex.ts'; import type { ErrorMessage, PipeResult } from '../../types.ts'; import { getOutput, getPipeIssues } from '../../utils/index.ts'; @@ -10,7 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function ulid(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^[\da-hjkmnp-tv-z]{26}$/iu.test(input) + !ULID_REGEX.test(input) ? getPipeIssues('ulid', error || 'Invalid ULID', input) : getOutput(input); } diff --git a/library/src/validations/uuid/uuid.ts b/library/src/validations/uuid/uuid.ts index 575334802..5bad3d7b6 100644 --- a/library/src/validations/uuid/uuid.ts +++ b/library/src/validations/uuid/uuid.ts @@ -1,8 +1,9 @@ +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. * @@ -10,7 +11,7 @@ import { getOutput, getPipeIssues } from '../../utils/index.ts'; */ export function uuid(error?: ErrorMessage) { return (input: TInput): PipeResult => - !/^[\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); }