diff --git a/library/CHANGELOG.md b/library/CHANGELOG.md index 1fdfa51f5..9c0b76803 100644 --- a/library/CHANGELOG.md +++ b/library/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the library will be documented in this file. +## vX.X.X (Month DD, YYYY) + +- Add `mac`, `mac48` and `mac64` validation function (pull request #270) + ## v0.22.0 (December 03, 2023) - Add support for boolean to `notValue` validation (pull request #261) diff --git a/library/src/regex.ts b/library/src/regex.ts index 8ae655c5f..ecc1d4003 100644 --- a/library/src/regex.ts +++ b/library/src/regex.ts @@ -65,6 +65,18 @@ export const ISO_TIMESTAMP_REGEX = */ export const ISO_WEEK_REGEX = /^\d{4}-W(?:0[1-9]|[1-4]\d|5[0-3])$/u; +/** + * [MAC](https://en.wikipedia.org/wiki/MAC_address) 48 bit regex. + */ +export const MAC48_REGEX = + /^(?:[\da-f]{2}:){5}[\da-f]{2}$|^(?:[\da-f]{2}-){5}[\da-f]{2}$|^(?:[\da-f]{4}\.){2}[\da-f]{4}$/iu; + +/** + * [MAC](https://en.wikipedia.org/wiki/MAC_address) 64 bit regex. + */ +export const MAC64_REGEX = + /^(?:[\da-f]{2}:){7}[\da-f]{2}$|^(?:[\da-f]{2}-){7}[\da-f]{2}$|^(?:[\da-f]{4}\.){3}[\da-f]{4}$|^(?:[\da-f]{4}:){3}[\da-f]{4}$/iu; + /** * [ULID](https://github.com/ulid/spec) regex. */ diff --git a/library/src/validations/index.ts b/library/src/validations/index.ts index c48408deb..8e11f75af 100644 --- a/library/src/validations/index.ts +++ b/library/src/validations/index.ts @@ -20,6 +20,9 @@ export * from './isoTimeSecond/index.ts'; export * from './isoTimestamp/index.ts'; export * from './isoWeek/index.ts'; export * from './length/index.ts'; +export * from './mac/index.ts'; +export * from './mac48/index.ts'; +export * from './mac64/index.ts'; export * from './maxBytes/index.ts'; export * from './maxLength/index.ts'; export * from './maxSize/index.ts'; diff --git a/library/src/validations/mac/index.ts b/library/src/validations/mac/index.ts new file mode 100644 index 000000000..8245c6754 --- /dev/null +++ b/library/src/validations/mac/index.ts @@ -0,0 +1 @@ +export * from './mac.ts'; diff --git a/library/src/validations/mac/mac.test.ts b/library/src/validations/mac/mac.test.ts new file mode 100644 index 000000000..66597b8ae --- /dev/null +++ b/library/src/validations/mac/mac.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, test } from 'vitest'; +import { mac } from './mac.ts'; + +describe('mac', () => { + test('should pass only MAC address', () => { + const validate = mac(); + + const value1 = 'b6:05:20:67:f9:58'; + expect(validate._parse(value1).output).toBe(value1); + const value2 = 'b6-05-20-67-f9-58'; + expect(validate._parse(value2).output).toBe(value2); + const value3 = 'b605.2067.f958'; + expect(validate._parse(value3).output).toBe(value3); + const value4 = '00:25:96:FF:FE:12:34:56'; + expect(validate._parse(value4).output).toBe(value4); + const value5 = '00-1A-2B-3C-4D-5E-6F-70'; + expect(validate._parse(value5).output).toBe(value5); + const value6 = '0025.96FF.FE12.3456'; + expect(validate._parse(value6).output).toBe(value6); + const value7 = '0025:96FF:FE12:3456'; + expect(validate._parse(value7).output).toBe(value7); + + expect(validate._parse('').issues).toBeTruthy(); + expect(validate._parse('00:1G:2B:3C:4D:5E').issues).toBeTruthy(); + expect(validate._parse('00:1A:2B:3C:4D').issues).toBeTruthy(); + expect(validate._parse('00:1A:2B:3C:4D:5E:6F').issues).toBeTruthy(); + expect(validate._parse('00:1A:2B:3C:4D:5E:6F:70:80').issues).toBeTruthy(); + expect(validate._parse('b605-2067-f958').issues).toBeTruthy(); + expect(validate._parse('00_1A_2B_3C_4D_5E').issues).toBeTruthy(); + expect(validate._parse('001A2B3C4D5E6F').issues).toBeTruthy(); + expect(validate._parse('ZZ:ZZ:ZZ:ZZ:ZZ:ZZ').issues).toBeTruthy(); + expect( + validate._parse('00:1A:2B:3C:4D:5E:6F:70:80:90:AB').issues + ).toBeTruthy(); + expect(validate._parse('001122334455').issues).toBeTruthy(); + expect(validate._parse('00:1A:2B:3C:4D:5E:6F:70:ZZ').issues).toBeTruthy(); + expect(validate._parse('GHIJ:KLNM:OPQR').issues).toBeTruthy(); + }); + + test('should return custom error message', () => { + const error = 'Value is not MAC address!'; + const validate = mac(error); + expect(validate._parse('test').issues?.[0].message).toBe(error); + }); +}); diff --git a/library/src/validations/mac/mac.ts b/library/src/validations/mac/mac.ts new file mode 100644 index 000000000..55cd0314e --- /dev/null +++ b/library/src/validations/mac/mac.ts @@ -0,0 +1,41 @@ +import { MAC48_REGEX, MAC64_REGEX } from '../../regex.ts'; +import type { BaseValidation, ErrorMessage } from '../../types/index.ts'; +import { actionIssue, actionOutput } from '../../utils/index.ts'; + +/** + * MAC validation type. + */ +export type MacValidation = BaseValidation & { + /** + * The validation type. + */ + type: 'mac'; + /** + * The MAC 48 and 64 bit regex. + */ + requirement: [RegExp, RegExp]; +}; + +/** + * Creates a validation function that validates a [MAC](https://en.wikipedia.org/wiki/MAC_address). + * + * @param message The error message. + * + * @returns A validation function. + */ +export function mac( + message: ErrorMessage = 'Invalid MAC' +): MacValidation { + return { + type: 'mac', + async: false, + message, + requirement: [MAC48_REGEX, MAC64_REGEX], + _parse(input) { + return !this.requirement[0].test(input) && + !this.requirement[1].test(input) + ? actionIssue(this.type, this.message, input, this.requirement) + : actionOutput(input); + }, + }; +} diff --git a/library/src/validations/mac48/index.ts b/library/src/validations/mac48/index.ts new file mode 100644 index 000000000..cec81fa18 --- /dev/null +++ b/library/src/validations/mac48/index.ts @@ -0,0 +1 @@ +export * from './mac48.ts'; diff --git a/library/src/validations/mac48/mac48.test.ts b/library/src/validations/mac48/mac48.test.ts new file mode 100644 index 000000000..4c1d823c0 --- /dev/null +++ b/library/src/validations/mac48/mac48.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, test } from 'vitest'; +import { mac48 } from './mac48.ts'; + +describe('mac48', () => { + test('should pass only MAC 48 address', () => { + const validate = mac48(); + + const value1 = 'b6:05:20:67:f9:58'; + expect(validate._parse(value1).output).toBe(value1); + const value2 = 'b6-05-20-67-f9-58'; + expect(validate._parse(value2).output).toBe(value2); + const value3 = 'b605.2067.f958'; + expect(validate._parse(value3).output).toBe(value3); + + expect(validate._parse('').issues).toBeTruthy(); + expect(validate._parse('00:1G:2B:3C:4D:5E').issues).toBeTruthy(); + expect(validate._parse('00:1A:2B:3C:4D').issues).toBeTruthy(); + expect(validate._parse('b605-2067-f958').issues).toBeTruthy(); + expect(validate._parse('00_1A_2B_3C_4D_5E').issues).toBeTruthy(); + expect(validate._parse('001A2B3C4D5E6F').issues).toBeTruthy(); + expect(validate._parse('ZZ:ZZ:ZZ:ZZ:ZZ:ZZ').issues).toBeTruthy(); + expect( + validate._parse('00:1A:2B:3C:4D:5E:6F:70:80:90:AB').issues + ).toBeTruthy(); + expect(validate._parse('001122334455').issues).toBeTruthy(); + expect(validate._parse('GHIJ:KLNM:OPQR').issues).toBeTruthy(); + }); + + test('should return custom error message', () => { + const error = 'Value is not MAC address!'; + const validate = mac48(error); + expect(validate._parse('test').issues?.[0].message).toBe(error); + }); +}); diff --git a/library/src/validations/mac48/mac48.ts b/library/src/validations/mac48/mac48.ts new file mode 100644 index 000000000..ee468e933 --- /dev/null +++ b/library/src/validations/mac48/mac48.ts @@ -0,0 +1,40 @@ +import { MAC48_REGEX } from '../../regex.ts'; +import type { BaseValidation, ErrorMessage } from '../../types/index.ts'; +import { actionIssue, actionOutput } from '../../utils/index.ts'; + +/** + * MAC validation type. + */ +export type Mac48Validation = BaseValidation & { + /** + * The validation type. + */ + type: 'mac48'; + /** + * The 48 bit MAC regex. + */ + requirement: RegExp; +}; + +/** + * Creates a validation function that validates a 48 bit [MAC](https://en.wikipedia.org/wiki/MAC_address). + * + * @param message The error message. + * + * @returns A validation function. + */ +export function mac48( + message: ErrorMessage = 'Invalid 48 bit MAC' +): Mac48Validation { + return { + type: 'mac48', + async: false, + message, + requirement: MAC48_REGEX, + _parse(input) { + return !this.requirement.test(input) + ? actionIssue(this.type, this.message, input, this.requirement) + : actionOutput(input); + }, + }; +} diff --git a/library/src/validations/mac64/index.ts b/library/src/validations/mac64/index.ts new file mode 100644 index 000000000..6fc2732ca --- /dev/null +++ b/library/src/validations/mac64/index.ts @@ -0,0 +1 @@ +export * from './mac64.ts'; diff --git a/library/src/validations/mac64/mac64.test.ts b/library/src/validations/mac64/mac64.test.ts new file mode 100644 index 000000000..4049ab0d5 --- /dev/null +++ b/library/src/validations/mac64/mac64.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test } from 'vitest'; +import { mac64 } from './mac64.ts'; + +describe('mac64', () => { + test('should pass only MAC address', () => { + const validate = mac64(); + + const value1 = '00:25:96:FF:FE:12:34:56'; + expect(validate._parse(value1).output).toBe(value1); + const value2 = '00-1A-2B-3C-4D-5E-6F-70'; + expect(validate._parse(value2).output).toBe(value2); + const value3 = '0025.96FF.FE12.3456'; + expect(validate._parse(value3).output).toBe(value3); + const value4 = '0025:96FF:FE12:3456'; + expect(validate._parse(value4).output).toBe(value4); + + expect(validate._parse('').issues).toBeTruthy(); + expect(validate._parse('00:1G:2B:3C:4D:5E').issues).toBeTruthy(); + expect(validate._parse('00:1A:2B:3C:4D:5E:6F').issues).toBeTruthy(); + expect(validate._parse('0025.96FF.FE12').issues).toBeTruthy(); + expect(validate._parse('00:1A:2B:3C:4D:5E:6F:70:80').issues).toBeTruthy(); + expect(validate._parse('b605-2067-f958').issues).toBeTruthy(); + expect(validate._parse('00_1A_2B_3C_4D_5E').issues).toBeTruthy(); + expect(validate._parse('001A2B3C4D5E6F').issues).toBeTruthy(); + expect(validate._parse('ZZ:ZZ:ZZ:ZZ:ZZ:ZZ').issues).toBeTruthy(); + expect( + validate._parse('00:1A:2B:3C:4D:5E:6F:70:80:90:AB').issues + ).toBeTruthy(); + expect(validate._parse('001122334455').issues).toBeTruthy(); + expect(validate._parse('00:1A:2B:3C:4D:5E:6F:70:ZZ').issues).toBeTruthy(); + expect(validate._parse('GHIJ:KLNM:OPQR').issues).toBeTruthy(); + }); + + test('should return custom error message', () => { + const error = 'Value is not MAC address!'; + const validate = mac64(error); + expect(validate._parse('test').issues?.[0].message).toBe(error); + }); +}); diff --git a/library/src/validations/mac64/mac64.ts b/library/src/validations/mac64/mac64.ts new file mode 100644 index 000000000..f74de1d8e --- /dev/null +++ b/library/src/validations/mac64/mac64.ts @@ -0,0 +1,40 @@ +import { MAC64_REGEX } from '../../regex.ts'; +import type { BaseValidation, ErrorMessage } from '../../types/index.ts'; +import { actionIssue, actionOutput } from '../../utils/index.ts'; + +/** + * MAC validation type. + */ +export type Mac64Validation = BaseValidation & { + /** + * The validation type. + */ + type: 'mac64'; + /** + * The 64 bit MAC regex. + */ + requirement: RegExp; +}; + +/** + * Creates a validation function that validates a 64 bit [MAC](https://en.wikipedia.org/wiki/MAC_address). + * + * @param message The error message. + * + * @returns A validation function. + */ +export function mac64( + message: ErrorMessage = 'Invalid 64 bit MAC' +): Mac64Validation { + return { + type: 'mac64', + async: false, + message, + requirement: MAC64_REGEX, + _parse(input) { + return !this.requirement.test(input) + ? actionIssue(this.type, this.message, input, this.requirement) + : actionOutput(input); + }, + }; +} diff --git a/library/tsconfig.json b/library/tsconfig.json index 8f72398dd..797bdb8c7 100644 --- a/library/tsconfig.json +++ b/library/tsconfig.json @@ -9,5 +9,5 @@ "skipLibCheck": true, "noEmit": true }, - "include": ["src", "./*.ts"] + "include": ["src", "./*.config.ts"] } diff --git a/website/src/routes/api/(schemas)/string/index.mdx b/website/src/routes/api/(schemas)/string/index.mdx index 2fa8164c8..2854ee1a1 100644 --- a/website/src/routes/api/(schemas)/string/index.mdx +++ b/website/src/routes/api/(schemas)/string/index.mdx @@ -127,6 +127,9 @@ The following APIs can be combined with `string`. 'isoTimestamp', 'isoWeek', 'length', + 'mac', + 'mac48', + 'mac64', 'maxBytes', 'maxLength', 'maxValue', diff --git a/website/src/routes/api/(validations)/mac/index.mdx b/website/src/routes/api/(validations)/mac/index.mdx new file mode 100644 index 000000000..934982acc --- /dev/null +++ b/website/src/routes/api/(validations)/mac/index.mdx @@ -0,0 +1,7 @@ +--- +title: mac +--- + +# mac + +> The content of this page is not yet ready. Until then just use the [source code](https://github.com/fabian-hiller/valibot/blob/main/library/src/validations/mac/mac.ts). diff --git a/website/src/routes/api/(validations)/mac48/index.mdx b/website/src/routes/api/(validations)/mac48/index.mdx new file mode 100644 index 000000000..de86b4dd5 --- /dev/null +++ b/website/src/routes/api/(validations)/mac48/index.mdx @@ -0,0 +1,7 @@ +--- +title: mac48 +--- + +# mac48 + +> The content of this page is not yet ready. Until then just use the [source code](https://github.com/fabian-hiller/valibot/blob/main/library/src/validations/mac48/mac48.ts). diff --git a/website/src/routes/api/(validations)/mac64/index.mdx b/website/src/routes/api/(validations)/mac64/index.mdx new file mode 100644 index 000000000..e33ecfdd7 --- /dev/null +++ b/website/src/routes/api/(validations)/mac64/index.mdx @@ -0,0 +1,7 @@ +--- +title: mac64 +--- + +# mac64 + +> The content of this page is not yet ready. Until then just use the [source code](https://github.com/fabian-hiller/valibot/blob/main/library/src/validations/mac64/mac64.ts). diff --git a/website/src/routes/api/menu.md b/website/src/routes/api/menu.md index c948cf319..12ec3eeea 100644 --- a/website/src/routes/api/menu.md +++ b/website/src/routes/api/menu.md @@ -81,6 +81,9 @@ - [isoTimestamp](/api/isoTimestamp) - [isoWeek](/api/isoWeek) - [length](/api/length) +- [mac](/api/mac) +- [mac48](/api/mac48) +- [mac64](/api/mac64) - [maxBytes](/api/maxBytes) - [maxLength](/api/maxLength) - [maxSize](/api/maxSize) diff --git a/website/src/routes/guides/(main-concepts)/pipelines/index.mdx b/website/src/routes/guides/(main-concepts)/pipelines/index.mdx index a6bb6229c..e641b9768 100644 --- a/website/src/routes/guides/(main-concepts)/pipelines/index.mdx +++ b/website/src/routes/guides/(main-concepts)/pipelines/index.mdx @@ -62,6 +62,9 @@ Validation functions examine the input and, if the input does not meet a certain 'isoTimestamp', 'isoWeek', 'length', + 'mac', + 'mac48', + 'mac64', 'maxBytes', 'maxLength', 'maxSize',