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

Add export for any validation regex #219

Merged
merged 2 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const IPV4_REGEX = /^(?:(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)\.?\b){4}$/u;
export const IPV4_REGEX = /^(?:25[0-5]|(?:2[0-4]|1\d|[1-9]|)\d)(?:\.(?:25[0-5]|(?:2[0-4]|1\d|[1-9]|)\d)){3}$/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);
}