From 364b4e2761c58ceabf42d9924a4d45efdefd4165 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Wed, 25 Sep 2024 10:17:36 -0700 Subject: [PATCH 1/8] Add CIDR related regex expressions --- library/src/regex.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/library/src/regex.ts b/library/src/regex.ts index b86089fc9..8f06cb07c 100644 --- a/library/src/regex.ts +++ b/library/src/regex.ts @@ -63,18 +63,36 @@ export const IPV4_REGEX: RegExp = // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive /^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}$/u; +/** + * [IPv4 CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#IPv4_CIDR_blocks) regex. + */ +export const IPV4_CIDR_REGEX: RegExp = + /^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}\/(?:[0-9]|[1-2][0-9]|3[0-2])$/u; + /** * [IPv6](https://en.wikipedia.org/wiki/IPv6) regex. */ export const IPV6_REGEX: RegExp = /^(?:(?:[\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; +/** + * [IPv6 CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#IPv6_CIDR_blocks) regex. + */ +export const IPV6_CIDR_REGEX: RegExp = + /^(?:(?:[\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))\/(?:[0-9]|[1-9][0-9]|1(?:[0-1][0-9]|2[0-8]))$/iu; + /** * [IP](https://en.wikipedia.org/wiki/IP_address) regex. */ export const IP_REGEX: RegExp = /^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}$|^(?:(?:[\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; +/** + * [IP CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) regex. + */ +export const IP_CIDR_REGEX: RegExp = + /^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}\/(?:[0-9]|[1-2][0-9]|3[0-2])$|^(?:(?:[\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))\/(?:[0-9]|[1-9][0-9]|1(?:[0-1][0-9]|2[0-8]))$/iu; + /** * [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date regex. */ From 5e918660caa6ceaee9d28759dd41a404f91828c5 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Wed, 25 Sep 2024 10:41:39 -0700 Subject: [PATCH 2/8] Add IP CIDR action --- library/src/actions/ipCidr/index.ts | 1 + library/src/actions/ipCidr/ipCidr.test-d.ts | 41 ++++++ library/src/actions/ipCidr/ipCidr.test.ts | 138 ++++++++++++++++++++ library/src/actions/ipCidr/ipCidr.ts | 102 +++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 library/src/actions/ipCidr/index.ts create mode 100644 library/src/actions/ipCidr/ipCidr.test-d.ts create mode 100644 library/src/actions/ipCidr/ipCidr.test.ts create mode 100644 library/src/actions/ipCidr/ipCidr.ts diff --git a/library/src/actions/ipCidr/index.ts b/library/src/actions/ipCidr/index.ts new file mode 100644 index 000000000..bc0617deb --- /dev/null +++ b/library/src/actions/ipCidr/index.ts @@ -0,0 +1 @@ +export * from './ipCidr.ts'; diff --git a/library/src/actions/ipCidr/ipCidr.test-d.ts b/library/src/actions/ipCidr/ipCidr.test-d.ts new file mode 100644 index 000000000..4fcad21d0 --- /dev/null +++ b/library/src/actions/ipCidr/ipCidr.test-d.ts @@ -0,0 +1,41 @@ +import { describe, expectTypeOf, test } from 'vitest'; +import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts'; +import { ipCidr, type IpCidrAction, type IpCidrIssue } from './ipCidr.ts'; + +describe('ipCidr', () => { + describe('should return action object', () => { + test('with undefined message', () => { + type Action = IpCidrAction; + expectTypeOf(ipCidr()).toEqualTypeOf(); + expectTypeOf(ipCidr(undefined)).toEqualTypeOf(); + }); + + test('with string message', () => { + expectTypeOf(ipCidr('message')).toEqualTypeOf< + IpCidrAction + >(); + }); + + test('with function message', () => { + expectTypeOf(ipCidr string>(() => 'message')).toEqualTypeOf< + IpCidrAction string> + >(); + }); + }); + + describe('should infer correct types', () => { + type Action = IpCidrAction; + + test('of input', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of output', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of issue', () => { + expectTypeOf>().toEqualTypeOf>(); + }); + }); +}); diff --git a/library/src/actions/ipCidr/ipCidr.test.ts b/library/src/actions/ipCidr/ipCidr.test.ts new file mode 100644 index 000000000..da15e9eb5 --- /dev/null +++ b/library/src/actions/ipCidr/ipCidr.test.ts @@ -0,0 +1,138 @@ +import { describe, expect, test } from 'vitest'; +import { IP_CIDR_REGEX } from '../../regex.ts'; +import { expectActionIssue, expectNoActionIssue } from '../../vitest/index.ts'; +import { ipCidr, type IpCidrAction, type IpCidrIssue } from './ipCidr.ts'; + +// TODO: Improve tests to cover all possible scenarios based on the regex used. + +describe('ipCidr', () => { + describe('should return action object', () => { + const baseAction: Omit, 'message'> = { + kind: 'validation', + type: 'ip_cidr', + reference: ipCidr, + expects: null, + requirement: IP_CIDR_REGEX, + async: false, + _run: expect.any(Function), + }; + + test('with undefined message', () => { + const action: IpCidrAction = { + ...baseAction, + message: undefined, + }; + expect(ipCidr()).toStrictEqual(action); + expect(ipCidr(undefined)).toStrictEqual(action); + }); + + test('with string message', () => { + expect(ipCidr('message')).toStrictEqual({ + ...baseAction, + message: 'message', + } satisfies IpCidrAction); + }); + + test('with function message', () => { + const message = () => 'message'; + expect(ipCidr(message)).toStrictEqual({ + ...baseAction, + message, + } satisfies IpCidrAction); + }); + }); + + describe('should return dataset without issues', () => { + const action = ipCidr(); + + test('for untyped inputs', () => { + expect(action._run({ typed: false, value: null }, {})).toStrictEqual({ + typed: false, + value: null, + }); + }); + + test('for IPv4 address', () => { + expectNoActionIssue(action, [ + '192.168.1.1/0', + '127.0.0.1/9', + '0.0.0.0/10', + '255.255.255.255/11', + '237.84.2.178/19', + '89.207.132.170/20', + '237.84.2.178/21', + '55.151.133.223/29', + '244.178.44.111/30', + '234.218.86.91/32', + ]); + }); + + test('for IPv6 address', () => { + expectNoActionIssue(action, [ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334/0', + 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329/9', + 'fe80::1ff:fe23:4567:890a/10', + '2001:db8:85a3:8d3:1319:8a2e:370:7348/11', + 'e05b:3266:e43f:3fdf:a34c:c11:dbc4:349f/19', + 'f053:688e:431:c36b:a452:425:8c88:713e/20', + '620a:9614:8852:a772:9c03:fd43:34a2:3c91/21', + '58e9:b974:fd6a:ff97:5376:22c2:321f:2144/99', + '7066:1b31:6757:e5cf:bd06:a46b:2e97:838f/100', + '3640:328f:e171:975:cbd1:1ac:5e72:7bfd/101', + 'f9c9:1876:9b59:c6f3:8a36:44a7:382d:1f50/110', + 'f48:9dbd:ae0e:4586:8209:38a4:a191:2b0e/119', + 'c159:7a14:58ba:ca7c:d3d3:98ce:6978:68e3/120', + '3457:589a:2291:1598:be2:16d7:4902:1e37/121', + '5eb9:bc94:d0fb:3f9a:173a:74c1:86ca:1ef7/128', + ]); + }); + }); + + describe('should return dataset with issues', () => { + const action = ipCidr('message'); + const baseIssue: Omit, 'input' | 'received'> = { + kind: 'validation', + type: 'ip_cidr', + expected: null, + message: 'message', + requirement: IP_CIDR_REGEX, + }; + + test('for empty strings', () => { + expectActionIssue(action, baseIssue, ['', ' ', '\n']); + }); + + test('for invalid IPv4 address', () => { + expectActionIssue(action, baseIssue, [ + '1/24', + '-1.0.0.0/24', + '0..0.0.0/12', + '1234.0.0.0/16', + '256.256.256.256/24', + '1.2.3/32', + '0.0.0.0.0/12', + 'a.a.a.a/10', + '237.84.2.178/33', + '82.78.37.80/-1', + '82.78.37.80/40', + '82.78.37.80/128', + ]); + }); + + test('for invalid IPv6 address', () => { + expectActionIssue(action, baseIssue, [ + 'd329:1be4:25b4:db47:a9d1:dc71:4926:992c:14af/32', + 'd5e7:7214:2b78::3906:85e6:53cc:709:32ba/40', + '8f69::c757:395e:976e::3441/64', + '54cb::473f:d516:0.255.256.22/72', + '54cb::473f:d516:192.168.1/86', + 'test:test:test:test:test:test:test:test/24', + 'f48:9dbd:ae0e:4586:8209:38a4:a191:2b0e/129', + '5b54:5d3e:b867:f367:2d53:8b26:ce9/-1', + 'f9c9:1876:9b59:c6f3:8a36:44a7:382d:1f50/130', + 'f48:9dbd:ae0e:4586:8209:38a4:a191:2b0e/200', + 'f48:9dbd:ae0e:4586:8209:38a4:a191:2b0e/1128', + ]); + }); + }); +}); diff --git a/library/src/actions/ipCidr/ipCidr.ts b/library/src/actions/ipCidr/ipCidr.ts new file mode 100644 index 000000000..654d98951 --- /dev/null +++ b/library/src/actions/ipCidr/ipCidr.ts @@ -0,0 +1,102 @@ +import { IP_CIDR_REGEX } from '../../regex.ts'; +import type { + BaseIssue, + BaseValidation, + Dataset, + ErrorMessage, +} from '../../types/index.ts'; +import { _addIssue } from '../../utils/index.ts'; + +/** + * IP CIDR issue type. + */ +export interface IpCidrIssue extends BaseIssue { + /** + * The issue kind. + */ + readonly kind: 'validation'; + /** + * The issue type. + */ + readonly type: 'ip_cidr'; + /** + * The expected property. + */ + readonly expected: null; + /** + * The received property. + */ + readonly received: `"${string}"`; + /** + * The IP CIDR regex. + */ + readonly requirement: RegExp; +} + +/** + * IP CIDR action type. + */ +export interface IpCidrAction< + TInput extends string, + TMessage extends ErrorMessage> | undefined, +> extends BaseValidation> { + /** + * The action type. + */ + readonly type: 'ip_cidr'; + /** + * The action reference. + */ + readonly reference: typeof ipCidr; + /** + * The expected property. + */ + readonly expects: null; + /** + * The IP CIDR regex. + */ + readonly requirement: RegExp; + /** + * The error message. + */ + readonly message: TMessage; +} + +/** + * Creates an [IP address in CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. + * + * @returns An IP CIDR action. + */ +export function ipCidr(): IpCidrAction; + +/** + * Creates an [IP address in CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. + * + * @param message The error message. + * + * @returns An IP CIDR action. + */ +export function ipCidr< + TInput extends string, + const TMessage extends ErrorMessage> | undefined, +>(message: TMessage): IpCidrAction; + +export function ipCidr( + message?: ErrorMessage> +): IpCidrAction> | undefined> { + return { + kind: 'validation', + type: 'ip_cidr', + reference: ipCidr, + async: false, + expects: null, + requirement: IP_CIDR_REGEX, + message, + _run(dataset, config) { + if (dataset.typed && !this.requirement.test(dataset.value)) { + _addIssue(this, 'IP-CIDR', dataset, config); + } + return dataset as Dataset>; + }, + }; +} From 16bbe95b2b5ff5334399c78baffc1095e0f6340b Mon Sep 17 00:00:00 2001 From: Mario564 Date: Wed, 25 Sep 2024 11:07:16 -0700 Subject: [PATCH 3/8] Update JSDoc for ipCidr action --- library/src/actions/ipCidr/ipCidr.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/actions/ipCidr/ipCidr.ts b/library/src/actions/ipCidr/ipCidr.ts index 654d98951..0ec69c46e 100644 --- a/library/src/actions/ipCidr/ipCidr.ts +++ b/library/src/actions/ipCidr/ipCidr.ts @@ -63,14 +63,14 @@ export interface IpCidrAction< } /** - * Creates an [IP address in CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. + * Creates an [IP address](https://en.wikipedia.org/wiki/IP_address) in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. * * @returns An IP CIDR action. */ export function ipCidr(): IpCidrAction; /** - * Creates an [IP address in CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. + * Creates an [IP address](https://en.wikipedia.org/wiki/IP_address) in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. * * @param message The error message. * From 064140b792136363ec318f950e83f1a200939142 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Wed, 25 Sep 2024 11:15:11 -0700 Subject: [PATCH 4/8] Add ipv4Cidr action --- library/src/actions/ipv4Cidr/index.ts | 1 + .../src/actions/ipv4Cidr/ipv4Cidr.test-d.ts | 41 +++++++ library/src/actions/ipv4Cidr/ipv4Cidr.test.ts | 104 ++++++++++++++++++ library/src/actions/ipv4Cidr/ipv4Cidr.ts | 102 +++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 library/src/actions/ipv4Cidr/index.ts create mode 100644 library/src/actions/ipv4Cidr/ipv4Cidr.test-d.ts create mode 100644 library/src/actions/ipv4Cidr/ipv4Cidr.test.ts create mode 100644 library/src/actions/ipv4Cidr/ipv4Cidr.ts diff --git a/library/src/actions/ipv4Cidr/index.ts b/library/src/actions/ipv4Cidr/index.ts new file mode 100644 index 000000000..d0aba0400 --- /dev/null +++ b/library/src/actions/ipv4Cidr/index.ts @@ -0,0 +1 @@ +export * from './ipv4Cidr.ts'; diff --git a/library/src/actions/ipv4Cidr/ipv4Cidr.test-d.ts b/library/src/actions/ipv4Cidr/ipv4Cidr.test-d.ts new file mode 100644 index 000000000..1949c6fb3 --- /dev/null +++ b/library/src/actions/ipv4Cidr/ipv4Cidr.test-d.ts @@ -0,0 +1,41 @@ +import { describe, expectTypeOf, test } from 'vitest'; +import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts'; +import { ipv4Cidr, type Ipv4CidrAction, type Ipv4CidrIssue } from './ipv4Cidr.ts'; + +describe('ipv4Cidr', () => { + describe('should return action object', () => { + test('with undefined message', () => { + type Action = Ipv4CidrAction; + expectTypeOf(ipv4Cidr()).toEqualTypeOf(); + expectTypeOf(ipv4Cidr(undefined)).toEqualTypeOf(); + }); + + test('with string message', () => { + expectTypeOf(ipv4Cidr('message')).toEqualTypeOf< + Ipv4CidrAction + >(); + }); + + test('with function message', () => { + expectTypeOf(ipv4Cidr string>(() => 'message')).toEqualTypeOf< + Ipv4CidrAction string> + >(); + }); + }); + + describe('should infer correct types', () => { + type Action = Ipv4CidrAction; + + test('of input', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of output', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of issue', () => { + expectTypeOf>().toEqualTypeOf>(); + }); + }); +}); diff --git a/library/src/actions/ipv4Cidr/ipv4Cidr.test.ts b/library/src/actions/ipv4Cidr/ipv4Cidr.test.ts new file mode 100644 index 000000000..a3289e22d --- /dev/null +++ b/library/src/actions/ipv4Cidr/ipv4Cidr.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, test } from 'vitest'; +import { IPV4_CIDR_REGEX } from '../../regex.ts'; +import { expectActionIssue, expectNoActionIssue } from '../../vitest/index.ts'; +import { ipv4Cidr, type Ipv4CidrAction, type Ipv4CidrIssue } from './ipv4Cidr.ts'; + +// TODO: Improve tests to cover all possible scenarios based on the regex used. + +describe('ipv4', () => { + describe('should return action object', () => { + const baseAction: Omit, 'message'> = { + kind: 'validation', + type: 'ipv4_cidr', + reference: ipv4Cidr, + expects: null, + requirement: IPV4_CIDR_REGEX, + async: false, + _run: expect.any(Function), + }; + + test('with undefined message', () => { + const action: Ipv4CidrAction = { + ...baseAction, + message: undefined, + }; + expect(ipv4Cidr()).toStrictEqual(action); + expect(ipv4Cidr(undefined)).toStrictEqual(action); + }); + + test('with string message', () => { + expect(ipv4Cidr('message')).toStrictEqual({ + ...baseAction, + message: 'message', + } satisfies Ipv4CidrAction); + }); + + test('with function message', () => { + const message = () => 'message'; + expect(ipv4Cidr(message)).toStrictEqual({ + ...baseAction, + message, + } satisfies Ipv4CidrAction); + }); + }); + + describe('should return dataset without issues', () => { + const action = ipv4Cidr(); + + test('for untyped inputs', () => { + expect(action._run({ typed: false, value: null }, {})).toStrictEqual({ + typed: false, + value: null, + }); + }); + + test('for IPv4 address', () => { + expectNoActionIssue(action, [ + '192.168.1.1/24', + '127.0.0.1/32', + '0.0.0.0/16', + '255.255.255.255/24', + ]); + }); + }); + + describe('should return dataset with issues', () => { + const action = ipv4Cidr('message'); + const baseIssue: Omit, 'input' | 'received'> = { + kind: 'validation', + type: 'ipv4_cidr', + expected: null, + message: 'message', + requirement: IPV4_CIDR_REGEX, + }; + + test('for empty strings', () => { + expectActionIssue(action, baseIssue, ['', ' ', '\n']); + }); + + test('for invalid IPv4 address', () => { + expectActionIssue(action, baseIssue, [ + '1/24', + '-1.0.0.0/24', + '0..0.0.0/12', + '1234.0.0.0/16', + '256.256.256.256/24', + '1.2.3/24', + '0.0.0.0.0/12', + 'a.a.a.a/24', + '192.0.0.1/33', + '82.78.37.80/-1', + '82.78.37.80/40', + ]); + }); + + test('for IPv6 address', () => { + expectActionIssue(action, baseIssue, [ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334/24', + 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329/12', + 'fe80::1ff:fe23:4567:890a/32', + '2001:db8:85a3:8d3:1319:8a2e:370:7348/128', + ]); + }); + }); +}); diff --git a/library/src/actions/ipv4Cidr/ipv4Cidr.ts b/library/src/actions/ipv4Cidr/ipv4Cidr.ts new file mode 100644 index 000000000..63ebd193e --- /dev/null +++ b/library/src/actions/ipv4Cidr/ipv4Cidr.ts @@ -0,0 +1,102 @@ +import { IPV4_CIDR_REGEX } from '../../regex.ts'; +import type { + BaseIssue, + BaseValidation, + Dataset, + ErrorMessage, +} from '../../types/index.ts'; +import { _addIssue } from '../../utils/index.ts'; + +/** + * IPv4 CIDR issue type. + */ +export interface Ipv4CidrIssue extends BaseIssue { + /** + * The issue kind. + */ + readonly kind: 'validation'; + /** + * The issue type. + */ + readonly type: 'ipv4_cidr'; + /** + * The expected property. + */ + readonly expected: null; + /** + * The received property. + */ + readonly received: `"${string}"`; + /** + * The IPv4 CIDR regex. + */ + readonly requirement: RegExp; +} + +/** + * IPv4 action type. + */ +export interface Ipv4CidrAction< + TInput extends string, + TMessage extends ErrorMessage> | undefined, +> extends BaseValidation> { + /** + * The action type. + */ + readonly type: 'ipv4_cidr'; + /** + * The action reference. + */ + readonly reference: typeof ipv4Cidr; + /** + * The expected property. + */ + readonly expects: null; + /** + * The IPv4 CIDR regex. + */ + readonly requirement: RegExp; + /** + * The error message. + */ + readonly message: TMessage; +} + +/** + * Creates an [IPv4](https://en.wikipedia.org/wiki/IPv4) address in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. + * + * @returns An IPv4 CIDR action. + */ +export function ipv4Cidr(): Ipv4CidrAction; + +/** + * Creates an [IPv4](https://en.wikipedia.org/wiki/IPv4) address in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. + * + * @param message The error message. + * + * @returns An IPv4 CIDR action. + */ +export function ipv4Cidr< + TInput extends string, + const TMessage extends ErrorMessage> | undefined, +>(message: TMessage): Ipv4CidrAction; + +export function ipv4Cidr( + message?: ErrorMessage> +): Ipv4CidrAction> | undefined> { + return { + kind: 'validation', + type: 'ipv4_cidr', + reference: ipv4Cidr, + async: false, + expects: null, + requirement: IPV4_CIDR_REGEX, + message, + _run(dataset, config) { + if (dataset.typed && !this.requirement.test(dataset.value)) { + _addIssue(this, 'IPv4-CIDR', dataset, config); + } + return dataset as Dataset>; + }, + }; +} From 5c00f6c060d6b6a8b5fa70443fefe2221f978f5d Mon Sep 17 00:00:00 2001 From: Mario564 Date: Wed, 25 Sep 2024 11:25:10 -0700 Subject: [PATCH 5/8] Add IPv6 CIDR action --- library/src/actions/ipv6Cidr/index.ts | 0 .../src/actions/ipv6Cidr/ipv6Cidr.test-d.ts | 41 +++++++ library/src/actions/ipv6Cidr/ipv6Cidr.test.ts | 108 ++++++++++++++++++ library/src/actions/ipv6Cidr/ipv6Cidr.ts | 102 +++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 library/src/actions/ipv6Cidr/index.ts create mode 100644 library/src/actions/ipv6Cidr/ipv6Cidr.test-d.ts create mode 100644 library/src/actions/ipv6Cidr/ipv6Cidr.test.ts create mode 100644 library/src/actions/ipv6Cidr/ipv6Cidr.ts diff --git a/library/src/actions/ipv6Cidr/index.ts b/library/src/actions/ipv6Cidr/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/library/src/actions/ipv6Cidr/ipv6Cidr.test-d.ts b/library/src/actions/ipv6Cidr/ipv6Cidr.test-d.ts new file mode 100644 index 000000000..9f545236c --- /dev/null +++ b/library/src/actions/ipv6Cidr/ipv6Cidr.test-d.ts @@ -0,0 +1,41 @@ +import { describe, expectTypeOf, test } from 'vitest'; +import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts'; +import { ipv6Cidr, type Ipv6CidrAction, type Ipv6CidrIssue } from './ipv6Cidr.ts'; + +describe('ipv6Cidr', () => { + describe('should return action object', () => { + test('with undefined message', () => { + type Action = Ipv6CidrAction; + expectTypeOf(ipv6Cidr()).toEqualTypeOf(); + expectTypeOf(ipv6Cidr(undefined)).toEqualTypeOf(); + }); + + test('with string message', () => { + expectTypeOf(ipv6Cidr('message')).toEqualTypeOf< + Ipv6CidrAction + >(); + }); + + test('with function message', () => { + expectTypeOf(ipv6Cidr string>(() => 'message')).toEqualTypeOf< + Ipv6CidrAction string> + >(); + }); + }); + + describe('should infer correct types', () => { + type Action = Ipv6CidrAction; + + test('of input', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of output', () => { + expectTypeOf>().toEqualTypeOf(); + }); + + test('of issue', () => { + expectTypeOf>().toEqualTypeOf>(); + }); + }); +}); diff --git a/library/src/actions/ipv6Cidr/ipv6Cidr.test.ts b/library/src/actions/ipv6Cidr/ipv6Cidr.test.ts new file mode 100644 index 000000000..5f6cbda6a --- /dev/null +++ b/library/src/actions/ipv6Cidr/ipv6Cidr.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, test } from 'vitest'; +import { IPV6_CIDR_REGEX } from '../../regex.ts'; +import { expectActionIssue, expectNoActionIssue } from '../../vitest/index.ts'; +import { ipv6Cidr, type Ipv6CidrAction, type Ipv6CidrIssue } from './ipv6Cidr.ts'; + +// TODO: Improve tests to cover all possible scenarios based on the regex used. + +describe('ipv6Cidr', () => { + describe('should return action object', () => { + const baseAction: Omit, 'message'> = { + kind: 'validation', + type: 'ipv6_cidr', + reference: ipv6Cidr, + expects: null, + requirement: IPV6_CIDR_REGEX, + async: false, + _run: expect.any(Function), + }; + + test('with undefined message', () => { + const action: Ipv6CidrAction = { + ...baseAction, + message: undefined, + }; + expect(ipv6Cidr()).toStrictEqual(action); + expect(ipv6Cidr(undefined)).toStrictEqual(action); + }); + + test('with string message', () => { + expect(ipv6Cidr('message')).toStrictEqual({ + ...baseAction, + message: 'message', + } satisfies Ipv6CidrAction); + }); + + test('with function message', () => { + const message = () => 'message'; + expect(ipv6Cidr(message)).toStrictEqual({ + ...baseAction, + message, + } satisfies Ipv6CidrAction); + }); + }); + + describe('should return dataset without issues', () => { + const action = ipv6Cidr(); + + test('for untyped inputs', () => { + expect(action._run({ typed: false, value: null }, {})).toStrictEqual({ + typed: false, + value: null, + }); + }); + + test('for ipv6Cidr address', () => { + expectNoActionIssue(action, [ + '2001:0db8:85a3:0000:0000:8a2e:0370:7334/12', + 'FE80:0000:0000:0000:0202:B3FF:FE1E:8329/64', + 'fe80::1ff:fe23:4567:890a/128', + '2001:db8:85a3:8d3:1319:8a2e:370:7348/96', + ]); + }); + }); + + describe('should return dataset with issues', () => { + const action = ipv6Cidr('message'); + const baseIssue: Omit, 'input' | 'received'> = { + kind: 'validation', + type: 'ipv6_cidr', + expected: null, + message: 'message', + requirement: IPV6_CIDR_REGEX, + }; + + test('for empty strings', () => { + expectActionIssue(action, baseIssue, ['', ' ', '\n']); + }); + + test('for invalid ipv6Cidr address', () => { + expectActionIssue(action, baseIssue, [ + 'd329:1be4:25b4:db47:a9d1:dc71:4926:992c:14af/12', + 'd5e7:7214:2b78::3906:85e6:53cc:709:32ba/64', + '8f69::c757:395e:976e::3441/128', + '54cb::473f:d516:0.255.256.22/48', + '54cb::473f:d516:192.168.1/96', + 'test:test:test:test:test:test:test:test/1', + '5eb9:bc94:d0fb:3f9a:173a:74c1:86ca:1ef7/129', + '5b54:5d3e:b867:f367:2d53:8b26:ce9/-1', + 'f9c9:1876:9b59:c6f3:8a36:44a7:382d:1f50/130', + 'f48:9dbd:ae0e:4586:8209:38a4:a191:2b0e/200', + 'f48:9dbd:ae0e:4586:8209:38a4:a191:2b0e/1128', + ]); + }); + + test('for IPv4 address', () => { + expectActionIssue(action, baseIssue, [ + '192.168.1.1/12', + '127.0.0.1/8', + '0.0.0.0/24', + '255.255.255.255/32', + '237.84.2.178/33', + '82.78.37.80/-1', + '82.78.37.80/40', + '82.78.37.80/128', + ]); + }); + }); +}); diff --git a/library/src/actions/ipv6Cidr/ipv6Cidr.ts b/library/src/actions/ipv6Cidr/ipv6Cidr.ts new file mode 100644 index 000000000..8f54adee7 --- /dev/null +++ b/library/src/actions/ipv6Cidr/ipv6Cidr.ts @@ -0,0 +1,102 @@ +import { IPV6_CIDR_REGEX } from '../../regex.ts'; +import type { + BaseIssue, + BaseValidation, + Dataset, + ErrorMessage, +} from '../../types/index.ts'; +import { _addIssue } from '../../utils/index.ts'; + +/** + * IPv6 CIDR issue type. + */ +export interface Ipv6CidrIssue extends BaseIssue { + /** + * The issue kind. + */ + readonly kind: 'validation'; + /** + * The issue type. + */ + readonly type: 'ipv6_cidr'; + /** + * The expected property. + */ + readonly expected: null; + /** + * The received property. + */ + readonly received: `"${string}"`; + /** + * The IPv6 CIDR regex. + */ + readonly requirement: RegExp; +} + +/** + * IPv6 CIDR action type. + */ +export interface Ipv6CidrAction< + TInput extends string, + TMessage extends ErrorMessage> | undefined, +> extends BaseValidation> { + /** + * The action type. + */ + readonly type: 'ipv6_cidr'; + /** + * The action reference. + */ + readonly reference: typeof ipv6Cidr; + /** + * The expected property. + */ + readonly expects: null; + /** + * The IPv6 CIDR regex. + */ + readonly requirement: RegExp; + /** + * The error message. + */ + readonly message: TMessage; +} + +/** + * Creates an [IPv6](https://en.wikipedia.org/wiki/IPv6) address in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. + * + * @returns An IPv6 CIDR action. + */ +export function ipv6Cidr(): Ipv6CidrAction; + +/** + * Creates an [IPv6](https://en.wikipedia.org/wiki/IPv6) in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) address validation action. + * + * @param message The error message. + * + * @returns An IPv6 CIDR action. + */ +export function ipv6Cidr< + TInput extends string, + const TMessage extends ErrorMessage> | undefined, +>(message: TMessage): Ipv6CidrAction; + +export function ipv6Cidr( + message?: ErrorMessage> +): Ipv6CidrAction> | undefined> { + return { + kind: 'validation', + type: 'ipv6_cidr', + reference: ipv6Cidr, + async: false, + expects: null, + requirement: IPV6_CIDR_REGEX, + message, + _run(dataset, config) { + if (dataset.typed && !this.requirement.test(dataset.value)) { + _addIssue(this, 'IPv6-CIDR', dataset, config); + } + return dataset as Dataset>; + }, + }; +} From cc080e605c5695d703474c75578a1e711a9b1d9f Mon Sep 17 00:00:00 2001 From: Mario564 Date: Wed, 25 Sep 2024 11:31:20 -0700 Subject: [PATCH 6/8] Lint and format --- library/src/actions/ipCidr/ipCidr.test-d.ts | 4 +++- library/src/actions/ipCidr/ipCidr.ts | 5 ++++- library/src/actions/ipv4Cidr/ipv4Cidr.test-d.ts | 16 +++++++++++----- library/src/actions/ipv4Cidr/ipv4Cidr.test.ts | 6 +++++- library/src/actions/ipv4Cidr/ipv4Cidr.ts | 8 ++++++-- library/src/actions/ipv6Cidr/ipv6Cidr.test-d.ts | 16 +++++++++++----- library/src/actions/ipv6Cidr/ipv6Cidr.test.ts | 6 +++++- library/src/actions/ipv6Cidr/ipv6Cidr.ts | 8 ++++++-- library/src/regex.ts | 9 +++++---- 9 files changed, 56 insertions(+), 22 deletions(-) diff --git a/library/src/actions/ipCidr/ipCidr.test-d.ts b/library/src/actions/ipCidr/ipCidr.test-d.ts index 4fcad21d0..17c81879f 100644 --- a/library/src/actions/ipCidr/ipCidr.test-d.ts +++ b/library/src/actions/ipCidr/ipCidr.test-d.ts @@ -7,7 +7,9 @@ describe('ipCidr', () => { test('with undefined message', () => { type Action = IpCidrAction; expectTypeOf(ipCidr()).toEqualTypeOf(); - expectTypeOf(ipCidr(undefined)).toEqualTypeOf(); + expectTypeOf( + ipCidr(undefined) + ).toEqualTypeOf(); }); test('with string message', () => { diff --git a/library/src/actions/ipCidr/ipCidr.ts b/library/src/actions/ipCidr/ipCidr.ts index 0ec69c46e..938d8db03 100644 --- a/library/src/actions/ipCidr/ipCidr.ts +++ b/library/src/actions/ipCidr/ipCidr.ts @@ -67,7 +67,10 @@ export interface IpCidrAction< * * @returns An IP CIDR action. */ -export function ipCidr(): IpCidrAction; +export function ipCidr(): IpCidrAction< + TInput, + undefined +>; /** * Creates an [IP address](https://en.wikipedia.org/wiki/IP_address) in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. diff --git a/library/src/actions/ipv4Cidr/ipv4Cidr.test-d.ts b/library/src/actions/ipv4Cidr/ipv4Cidr.test-d.ts index 1949c6fb3..af1857f6e 100644 --- a/library/src/actions/ipv4Cidr/ipv4Cidr.test-d.ts +++ b/library/src/actions/ipv4Cidr/ipv4Cidr.test-d.ts @@ -1,13 +1,19 @@ import { describe, expectTypeOf, test } from 'vitest'; import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts'; -import { ipv4Cidr, type Ipv4CidrAction, type Ipv4CidrIssue } from './ipv4Cidr.ts'; +import { + ipv4Cidr, + type Ipv4CidrAction, + type Ipv4CidrIssue, +} from './ipv4Cidr.ts'; describe('ipv4Cidr', () => { describe('should return action object', () => { test('with undefined message', () => { type Action = Ipv4CidrAction; expectTypeOf(ipv4Cidr()).toEqualTypeOf(); - expectTypeOf(ipv4Cidr(undefined)).toEqualTypeOf(); + expectTypeOf( + ipv4Cidr(undefined) + ).toEqualTypeOf(); }); test('with string message', () => { @@ -17,9 +23,9 @@ describe('ipv4Cidr', () => { }); test('with function message', () => { - expectTypeOf(ipv4Cidr string>(() => 'message')).toEqualTypeOf< - Ipv4CidrAction string> - >(); + expectTypeOf( + ipv4Cidr string>(() => 'message') + ).toEqualTypeOf string>>(); }); }); diff --git a/library/src/actions/ipv4Cidr/ipv4Cidr.test.ts b/library/src/actions/ipv4Cidr/ipv4Cidr.test.ts index a3289e22d..66046fd50 100644 --- a/library/src/actions/ipv4Cidr/ipv4Cidr.test.ts +++ b/library/src/actions/ipv4Cidr/ipv4Cidr.test.ts @@ -1,7 +1,11 @@ import { describe, expect, test } from 'vitest'; import { IPV4_CIDR_REGEX } from '../../regex.ts'; import { expectActionIssue, expectNoActionIssue } from '../../vitest/index.ts'; -import { ipv4Cidr, type Ipv4CidrAction, type Ipv4CidrIssue } from './ipv4Cidr.ts'; +import { + ipv4Cidr, + type Ipv4CidrAction, + type Ipv4CidrIssue, +} from './ipv4Cidr.ts'; // TODO: Improve tests to cover all possible scenarios based on the regex used. diff --git a/library/src/actions/ipv4Cidr/ipv4Cidr.ts b/library/src/actions/ipv4Cidr/ipv4Cidr.ts index 63ebd193e..b4d109393 100644 --- a/library/src/actions/ipv4Cidr/ipv4Cidr.ts +++ b/library/src/actions/ipv4Cidr/ipv4Cidr.ts @@ -10,7 +10,8 @@ import { _addIssue } from '../../utils/index.ts'; /** * IPv4 CIDR issue type. */ -export interface Ipv4CidrIssue extends BaseIssue { +export interface Ipv4CidrIssue + extends BaseIssue { /** * The issue kind. */ @@ -67,7 +68,10 @@ export interface Ipv4CidrAction< * * @returns An IPv4 CIDR action. */ -export function ipv4Cidr(): Ipv4CidrAction; +export function ipv4Cidr(): Ipv4CidrAction< + TInput, + undefined +>; /** * Creates an [IPv4](https://en.wikipedia.org/wiki/IPv4) address in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. diff --git a/library/src/actions/ipv6Cidr/ipv6Cidr.test-d.ts b/library/src/actions/ipv6Cidr/ipv6Cidr.test-d.ts index 9f545236c..4fd23b6e3 100644 --- a/library/src/actions/ipv6Cidr/ipv6Cidr.test-d.ts +++ b/library/src/actions/ipv6Cidr/ipv6Cidr.test-d.ts @@ -1,13 +1,19 @@ import { describe, expectTypeOf, test } from 'vitest'; import type { InferInput, InferIssue, InferOutput } from '../../types/index.ts'; -import { ipv6Cidr, type Ipv6CidrAction, type Ipv6CidrIssue } from './ipv6Cidr.ts'; +import { + ipv6Cidr, + type Ipv6CidrAction, + type Ipv6CidrIssue, +} from './ipv6Cidr.ts'; describe('ipv6Cidr', () => { describe('should return action object', () => { test('with undefined message', () => { type Action = Ipv6CidrAction; expectTypeOf(ipv6Cidr()).toEqualTypeOf(); - expectTypeOf(ipv6Cidr(undefined)).toEqualTypeOf(); + expectTypeOf( + ipv6Cidr(undefined) + ).toEqualTypeOf(); }); test('with string message', () => { @@ -17,9 +23,9 @@ describe('ipv6Cidr', () => { }); test('with function message', () => { - expectTypeOf(ipv6Cidr string>(() => 'message')).toEqualTypeOf< - Ipv6CidrAction string> - >(); + expectTypeOf( + ipv6Cidr string>(() => 'message') + ).toEqualTypeOf string>>(); }); }); diff --git a/library/src/actions/ipv6Cidr/ipv6Cidr.test.ts b/library/src/actions/ipv6Cidr/ipv6Cidr.test.ts index 5f6cbda6a..b184ae7a2 100644 --- a/library/src/actions/ipv6Cidr/ipv6Cidr.test.ts +++ b/library/src/actions/ipv6Cidr/ipv6Cidr.test.ts @@ -1,7 +1,11 @@ import { describe, expect, test } from 'vitest'; import { IPV6_CIDR_REGEX } from '../../regex.ts'; import { expectActionIssue, expectNoActionIssue } from '../../vitest/index.ts'; -import { ipv6Cidr, type Ipv6CidrAction, type Ipv6CidrIssue } from './ipv6Cidr.ts'; +import { + ipv6Cidr, + type Ipv6CidrAction, + type Ipv6CidrIssue, +} from './ipv6Cidr.ts'; // TODO: Improve tests to cover all possible scenarios based on the regex used. diff --git a/library/src/actions/ipv6Cidr/ipv6Cidr.ts b/library/src/actions/ipv6Cidr/ipv6Cidr.ts index 8f54adee7..2f94f73f9 100644 --- a/library/src/actions/ipv6Cidr/ipv6Cidr.ts +++ b/library/src/actions/ipv6Cidr/ipv6Cidr.ts @@ -10,7 +10,8 @@ import { _addIssue } from '../../utils/index.ts'; /** * IPv6 CIDR issue type. */ -export interface Ipv6CidrIssue extends BaseIssue { +export interface Ipv6CidrIssue + extends BaseIssue { /** * The issue kind. */ @@ -67,7 +68,10 @@ export interface Ipv6CidrAction< * * @returns An IPv6 CIDR action. */ -export function ipv6Cidr(): Ipv6CidrAction; +export function ipv6Cidr(): Ipv6CidrAction< + TInput, + undefined +>; /** * Creates an [IPv6](https://en.wikipedia.org/wiki/IPv6) in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) address validation action. diff --git a/library/src/regex.ts b/library/src/regex.ts index 8f06cb07c..adb4b94b5 100644 --- a/library/src/regex.ts +++ b/library/src/regex.ts @@ -66,8 +66,9 @@ export const IPV4_REGEX: RegExp = /** * [IPv4 CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#IPv4_CIDR_blocks) regex. */ -export const IPV4_CIDR_REGEX: RegExp = - /^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}\/(?:[0-9]|[1-2][0-9]|3[0-2])$/u; +export const IPV4_CIDR_REGEX: RegExp = + // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive + /^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}\/(?:\d|[1|2]\d|3[0-2])$/u; /** * [IPv6](https://en.wikipedia.org/wiki/IPv6) regex. @@ -79,7 +80,7 @@ export const IPV6_REGEX: RegExp = * [IPv6 CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#IPv6_CIDR_blocks) regex. */ export const IPV6_CIDR_REGEX: RegExp = - /^(?:(?:[\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))\/(?:[0-9]|[1-9][0-9]|1(?:[0-1][0-9]|2[0-8]))$/iu; + /^(?:(?:[\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))\/(?:\d|[1-9]\d|1(?:[0|1]\d|2[0-8]))$/iu; /** * [IP](https://en.wikipedia.org/wiki/IP_address) regex. @@ -91,7 +92,7 @@ export const IP_REGEX: RegExp = * [IP CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) regex. */ export const IP_CIDR_REGEX: RegExp = - /^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}\/(?:[0-9]|[1-2][0-9]|3[0-2])$|^(?:(?:[\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))\/(?:[0-9]|[1-9][0-9]|1(?:[0-1][0-9]|2[0-8]))$/iu; + /^(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])(?:\.(?:(?:[1-9]|1\d|2[0-4])?\d|25[0-5])){3}\/(?:\d|[1|2]\d|3[0-2])$|^(?:(?:[\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))\/(?:\d|[1-9]\d|1(?:[0|1]\d|2[0-8]))$/iu; /** * [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date regex. From 82a194b44a7b6c67dc6569d7dc260c18e34266c5 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Wed, 25 Sep 2024 11:36:45 -0700 Subject: [PATCH 7/8] Add missing export --- library/src/actions/ipv6Cidr/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/library/src/actions/ipv6Cidr/index.ts b/library/src/actions/ipv6Cidr/index.ts index e69de29bb..de82ded96 100644 --- a/library/src/actions/ipv6Cidr/index.ts +++ b/library/src/actions/ipv6Cidr/index.ts @@ -0,0 +1 @@ +export * from './ipv6Cidr.ts'; \ No newline at end of file From 12aa601ba36887fa6fbad33fc031ed9a2494a278 Mon Sep 17 00:00:00 2001 From: Mario564 Date: Wed, 25 Sep 2024 11:54:59 -0700 Subject: [PATCH 8/8] Update docs --- .../src/routes/api/(actions)/ipCidr/index.mdx | 70 +++++++++++++++++++ .../routes/api/(actions)/ipCidr/properties.ts | 58 +++++++++++++++ .../routes/api/(actions)/ipv4Cidr/index.mdx | 67 ++++++++++++++++++ .../api/(actions)/ipv4Cidr/properties.ts | 58 +++++++++++++++ .../routes/api/(actions)/ipv6Cidr/index.mdx | 67 ++++++++++++++++++ .../api/(actions)/ipv6Cidr/properties.ts | 58 +++++++++++++++ website/src/routes/api/menu.md | 3 + 7 files changed, 381 insertions(+) create mode 100644 website/src/routes/api/(actions)/ipCidr/index.mdx create mode 100644 website/src/routes/api/(actions)/ipCidr/properties.ts create mode 100644 website/src/routes/api/(actions)/ipv4Cidr/index.mdx create mode 100644 website/src/routes/api/(actions)/ipv4Cidr/properties.ts create mode 100644 website/src/routes/api/(actions)/ipv6Cidr/index.mdx create mode 100644 website/src/routes/api/(actions)/ipv6Cidr/properties.ts diff --git a/website/src/routes/api/(actions)/ipCidr/index.mdx b/website/src/routes/api/(actions)/ipCidr/index.mdx new file mode 100644 index 000000000..1b2c2433a --- /dev/null +++ b/website/src/routes/api/(actions)/ipCidr/index.mdx @@ -0,0 +1,70 @@ +--- +title: ipCidr +description: Creates an IP address in CIDR notation validation action. +source: /actions/ipCidr/ipCidr.ts +contributors: + - fabian-hiller + - L-Mario564 +--- + +import { Link } from '@builder.io/qwik-city'; +import { ApiList, Property } from '~/components'; +import { properties } from './properties'; + +# ipCidr + +Creates an [IP address](https://en.wikipedia.org/wiki/IP_address) in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. + +> This validation action accepts IPv4 and IPv6 addresses. For a more specific validation, you can also use `ipv4Cidr` or `ipv6Cidr`. + +```ts +const Action = v.ipCidr(message); +``` + +## Generics + +- `TInput` +- `TMessage` + +## Parameters + +- `message` + +### Explanation + +With `ipCidr` you can validate the formatting of a string. If the input is not an IP address in CIDR notation, you can use `message` to customize the error message. + +## Returns + +- `Action` + +## Examples + +The following examples show how `ipCidr` can be used. + +### IP address in CIDR notation schema + +Schema to validate an IP address in CIDR notation. + +```ts +const IpAddressCidrNotationSchema = v.pipe( + v.string(), + v.ipCidr('The IP address is badly formatted.') +); +``` + +## Related + +The following APIs can be combined with `ipCidr`. + +### Schemas + + + +### Methods + + + +### Utils + + diff --git a/website/src/routes/api/(actions)/ipCidr/properties.ts b/website/src/routes/api/(actions)/ipCidr/properties.ts new file mode 100644 index 000000000..4c5286ffb --- /dev/null +++ b/website/src/routes/api/(actions)/ipCidr/properties.ts @@ -0,0 +1,58 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TInput: { + modifier: 'extends', + type: 'string', + }, + TMessage: { + modifier: 'extends', + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'ErrorMessage', + href: '../ErrorMessage/', + generics: [ + { + type: 'custom', + name: 'IpCidrIssue', + href: '../IpCidrIssue/', + generics: [ + { + type: 'custom', + name: 'TInput', + }, + ], + }, + ], + }, + 'undefined', + ], + }, + }, + message: { + type: { + type: 'custom', + name: 'TMessage', + }, + }, + Action: { + type: { + type: 'custom', + name: 'IpCidrAction', + href: '../IpCidrAction/', + generics: [ + { + type: 'custom', + name: 'TInput', + }, + { + type: 'custom', + name: 'TMessage', + }, + ], + }, + }, +}; diff --git a/website/src/routes/api/(actions)/ipv4Cidr/index.mdx b/website/src/routes/api/(actions)/ipv4Cidr/index.mdx new file mode 100644 index 000000000..09cf0e3bd --- /dev/null +++ b/website/src/routes/api/(actions)/ipv4Cidr/index.mdx @@ -0,0 +1,67 @@ +--- +title: ipv4Cidr +description: Creates an IPv4 address in CIDR notation validation action. +source: /actions/ipv4/ipv4.ts +contributors: + - fabian-hiller + - L-Mario564 +--- + +import { ApiList, Property } from '~/components'; +import { properties } from './properties'; + +# ipv4Cidr + +Creates an [IPv4](https://en.wikipedia.org/wiki/IPv4) address in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) validation action. + +```ts +const Action = v.ipv4Cidr(message); +``` + +## Generics + +- `TInput` +- `TMessage` + +## Parameters + +- `message` + +### Explanation + +With `ipv4Cidr` you can validate the formatting of a string. If the input is not an IPv4 address in CIDR notation, you can use `message` to customize the error message. + +## Returns + +- `Action` + +## Examples + +The following examples show how `ipv4Cidr` can be used. + +### IPv4 in CIDR notation schema + +Schema to validate an IPv4 address in CIDR notation. + +```ts +const Ipv4CidrSchema = v.pipe( + v.string(), + v.ipv4Cidr('The IP address is badly formatted.') +); +``` + +## Related + +The following APIs can be combined with `ipv4Cidr`. + +### Schemas + + + +### Methods + + + +### Utils + + diff --git a/website/src/routes/api/(actions)/ipv4Cidr/properties.ts b/website/src/routes/api/(actions)/ipv4Cidr/properties.ts new file mode 100644 index 000000000..4ef64faea --- /dev/null +++ b/website/src/routes/api/(actions)/ipv4Cidr/properties.ts @@ -0,0 +1,58 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TInput: { + modifier: 'extends', + type: 'string', + }, + TMessage: { + modifier: 'extends', + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'ErrorMessage', + href: '../ErrorMessage/', + generics: [ + { + type: 'custom', + name: 'Ipv4CidrIssue', + href: '../Ipv4CidrIssue/', + generics: [ + { + type: 'custom', + name: 'TInput', + }, + ], + }, + ], + }, + 'undefined', + ], + }, + }, + message: { + type: { + type: 'custom', + name: 'TMessage', + }, + }, + Action: { + type: { + type: 'custom', + name: 'Ipv4CidrAction', + href: '../Ipv4CidrAction/', + generics: [ + { + type: 'custom', + name: 'TInput', + }, + { + type: 'custom', + name: 'TMessage', + }, + ], + }, + }, +}; diff --git a/website/src/routes/api/(actions)/ipv6Cidr/index.mdx b/website/src/routes/api/(actions)/ipv6Cidr/index.mdx new file mode 100644 index 000000000..252737a3e --- /dev/null +++ b/website/src/routes/api/(actions)/ipv6Cidr/index.mdx @@ -0,0 +1,67 @@ +--- +title: ipv6Cidr +description: Creates an IPv6 address in CIDR notation validation action. +source: /actions/ipv6Cidr/ipv6Cidr.ts +contributors: + - fabian-hiller + - L-Mario564 +--- + +import { ApiList, Property } from '~/components'; +import { properties } from './properties'; + +# ipv6Cidr + +Creates an [IPv6](https://en.wikipedia.org/wiki/IPv6) in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) address validation action. + +```ts +const Action = v.ipv6Cidr(message); +``` + +## Generics + +- `TInput` +- `TMessage` + +## Parameters + +- `message` + +### Explanation + +With `ipv6Cidr` you can validate the formatting of a string. If the input is not an IPv6 address in CIDR notation, you can use `message` to customize the error message. + +## Returns + +- `Action` + +## Examples + +The following examples show how `ipv6Cidr` can be used. + +### IPv6 in CIDR notation schema + +Schema to validate an IPv6 address in CIDR notation. + +```ts +const Ipv6CidrSchema = v.pipe( + v.string(), + v.ipv6Cidr('The IP address is badly formatted.') +); +``` + +## Related + +The following APIs can be combined with `ipv6Cidr`. + +### Schemas + + + +### Methods + + + +### Utils + + diff --git a/website/src/routes/api/(actions)/ipv6Cidr/properties.ts b/website/src/routes/api/(actions)/ipv6Cidr/properties.ts new file mode 100644 index 000000000..efeede51f --- /dev/null +++ b/website/src/routes/api/(actions)/ipv6Cidr/properties.ts @@ -0,0 +1,58 @@ +import type { PropertyProps } from '~/components'; + +export const properties: Record = { + TInput: { + modifier: 'extends', + type: 'string', + }, + TMessage: { + modifier: 'extends', + type: { + type: 'union', + options: [ + { + type: 'custom', + name: 'ErrorMessage', + href: '../ErrorMessage/', + generics: [ + { + type: 'custom', + name: 'Ipv6CidrIssue', + href: '../Ipv6CidrIssue/', + generics: [ + { + type: 'custom', + name: 'TInput', + }, + ], + }, + ], + }, + 'undefined', + ], + }, + }, + message: { + type: { + type: 'custom', + name: 'TMessage', + }, + }, + Action: { + type: { + type: 'custom', + name: 'Ipv6CidrAction', + href: '../Ipv6CidrAction/', + generics: [ + { + type: 'custom', + name: 'TInput', + }, + { + type: 'custom', + name: 'TMessage', + }, + ], + }, + }, +}; diff --git a/website/src/routes/api/menu.md b/website/src/routes/api/menu.md index 7967cad6d..e72d311c8 100644 --- a/website/src/routes/api/menu.md +++ b/website/src/routes/api/menu.md @@ -100,8 +100,11 @@ - [includes](/api/includes/) - [integer](/api/integer/) - [ip](/api/ip/) +- [ipCidr](/api/ipCidr) - [ipv4](/api/ipv4/) +- [ipv4Cidr](/api/ipv4Cidr) - [ipv6](/api/ipv6/) +- [ipv6Cidr](/api/ipv6Cidr) - [isoDate](/api/isoDate/) - [isoDateTime](/api/isoDateTime/) - [isoTime](/api/isoTime/)