From b88c09e0c8c9df8e1a773e0968942cfdba385d8c Mon Sep 17 00:00:00 2001 From: mNik033 Date: Mon, 27 Oct 2025 23:24:27 +0530 Subject: [PATCH] feat(validations): Add IPv6 validator Add the isIPv6 function to the validation module to check for valid IPv6 addresses --- README.md | 20 ++++++++ src/tests/validations/isIPv6.test.ts | 71 ++++++++++++++++++++++++++++ src/validations/index.ts | 3 ++ src/validations/isIPv6.ts | 45 ++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 src/tests/validations/isIPv6.test.ts create mode 100644 src/validations/isIPv6.ts diff --git a/README.md b/README.md index 8979d43..d3e988c 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ These changes improve throughput and reduce memory pressure when working with la - [isSlug](#isslug) - Checks if a string is a valid slug - [isTypeOf](#istypeof) - Checks if a file or URL has a valid extension for a given type - [isIPv4](#isipv4) - Checks if a string is a valid IPv4 address +- [isIPv6](#isipv6) - Checks if a string is a valid IPv6 address - [isHexColor](#ishexcolor) - Checks if the input string is a valid hex color - [isPalindrome](#ispalindrome) - Checks if the input string is a palindrome (ignores case, spaces, and punctuation) - [isCoordinates](#iscoordinates) - Checks if given latitude and longitude are valid coordinates @@ -725,6 +726,25 @@ isIPv4('192.168.1.a'); // false (non-numeric) | --------- | ------ | -------- | -------------------------------------------- | | text | string | required | The input string to validate as IPv4 address | +#### `isIPv6(text)` + +Checks if a string is a valid IPv6 address. + +```javascript +import { isIPv6 } from 'stringzy'; + +isIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334'); // true +isIPv6('0:0:0:0:0:0:0:1'); // true +isIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334:1234'); // false (too many groups) +isIPv6('2001:db8:::1'); // false (invalid use of shorthand) +isIPv6('12345::abcd'); // false (out of range) +isIPv6('2001:db8::g1'); // false (non-hex character) +``` + +| Parameter | Type | Default | Description | +| --------- | ------ | -------- | -------------------------------------------- | +| text | string | required | The input string to validate as IPv6 address | + #### `isHexColor(text)` Checks if a string is a valid Hex color. diff --git a/src/tests/validations/isIPv6.test.ts b/src/tests/validations/isIPv6.test.ts new file mode 100644 index 0000000..a4a6da4 --- /dev/null +++ b/src/tests/validations/isIPv6.test.ts @@ -0,0 +1,71 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { isIPv6 } from '../../validations/isIPv6'; + +describe('isIPv6', () => { + it('returns true for valid full IPv6 addresses', () => { + assert.strictEqual(isIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334'), true); + assert.strictEqual(isIPv6('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), true); + assert.strictEqual(isIPv6('0:0:0:0:0:0:0:1'), true); + assert.strictEqual(isIPv6('fe80:0000:0000:0000:0202:b3ff:fe1e:8329'), true); + }); + + it('returns true for valid shorthand IPv6 addresses', () => { + assert.strictEqual(isIPv6('2001:db8:85a3::8a2e:370:7334'), true); + assert.strictEqual(isIPv6('::1'), true); + assert.strictEqual(isIPv6('fe80::1'), true); + assert.strictEqual(isIPv6('::'), true); + }); + + it('returns false for IPv6 addresses with too many groups', () => { + assert.strictEqual(isIPv6('2001:0db8:85a3:0000:0000:8a2e:0370:7334:1234'), false); + assert.strictEqual(isIPv6('1:2:3:4:5:6:7:8:9'), false); + }); + + it('returns false for IPv6 addresses with too few groups without shorthand', () => { + assert.strictEqual(isIPv6('2001:db8:85a3:8a2e:370:7334'), false); + assert.strictEqual(isIPv6('1:2:3:4:5:6:7'), false); + }); + + it('returns false for invalid use of shorthand (::)', () => { + assert.strictEqual(isIPv6('2001:db8:::1'), false); + assert.strictEqual(isIPv6(':::1'), false); + assert.strictEqual(isIPv6('2001::85a3::7334'), false); + }); + + it('returns false for groups longer than 4 hex digits', () => { + assert.strictEqual(isIPv6('12345::abcd'), false); + assert.strictEqual(isIPv6('2001:db8:85a3:00000:0000:8a2e:0370:7334'), false); + }); + + it('returns false for invalid characters', () => { + assert.strictEqual(isIPv6('2001:db8:85a3:z000:0000:8a2e:0370:7334'), false); + assert.strictEqual(isIPv6('2001:db8:85a3:0000:0000:8a2e:0370:g334'), false); + assert.strictEqual(isIPv6('abcd:efgh:ijkl:mnop:qrst:uvwx:yz12:3456'), false); + }); + + it('returns false for special characters and malformed strings', () => { + assert.strictEqual(isIPv6('2001:db8:85a3:0000:0000:8a2e:0370:7334:'), false); + assert.strictEqual(isIPv6(':2001:db8:85a3:0000:0000:8a2e:0370:7334'), false); + assert.strictEqual(isIPv6('2001:db8::85a3::7334'), false); + assert.strictEqual(isIPv6('2001:db8::85a3:7334:'), false); + }); + + it('returns false for empty strings and edge cases', () => { + assert.strictEqual(isIPv6(''), false); + assert.strictEqual(isIPv6(':'), false); + assert.strictEqual(isIPv6(':::'), false); + assert.strictEqual(isIPv6('::::'), false); + }); + + it('returns true for uppercase valid IPv6 addresses (case-insensitive)', () => { + assert.strictEqual(isIPv6('2001:DB8:85A3::8A2E:370:7334'), true); + assert.strictEqual(isIPv6('FE80::1'), true); + assert.strictEqual(isIPv6('FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF'), true); + }); + + it('returns false for IPv4-mapped IPv6 (unsupported by this validator)', () => { + assert.strictEqual(isIPv6('::ffff:192.168.1.1'), false); + assert.strictEqual(isIPv6('2001:db8::192.168.1.1'), false); + }); +}); diff --git a/src/validations/index.ts b/src/validations/index.ts index 666e4a8..35754f4 100644 --- a/src/validations/index.ts +++ b/src/validations/index.ts @@ -5,6 +5,7 @@ export { isEmpty } from './isEmpty'; export { isSlug } from './isSlug'; export { isURL } from './isURL'; export { isIPv4 } from './isIPv4'; +export { isIPv6 } from './isIPv6'; export { isHexColor } from './isHexColor'; export { isPalindrome } from './isPalindrome' export {isLowerCase} from './isLowerCase'; @@ -22,6 +23,7 @@ import { isEmpty } from './isEmpty'; import { isSlug } from './isSlug'; import { isURL } from './isURL'; import { isIPv4 } from './isIPv4'; +import { isIPv6 } from './isIPv6'; import { isHexColor } from './isHexColor'; import { isPalindrome } from './isPalindrome'; import { isLowerCase } from './isLowerCase'; @@ -40,6 +42,7 @@ export const validations = { isSlug, isURL, isIPv4, + isIPv6, isHexColor, isPalindrome, isLowerCase, diff --git a/src/validations/isIPv6.ts b/src/validations/isIPv6.ts new file mode 100644 index 0000000..ff63bbb --- /dev/null +++ b/src/validations/isIPv6.ts @@ -0,0 +1,45 @@ +/** + * Checks if a given string is a valid IPv6 address. + * + * Valid IPv6 addresses consist of eight groups of four hexadecimal digits (0-9, a-f, A-F) + * separated by colons (:). Each group can have 1 to 4 hex digits. + * Leading zeros are allowed. IPv6 addresses can also use the "::" shorthand once + * to represent one or more groups of zeros. + * + * @param {string} str - The string to validate as an IPv6 address. + * @returns {boolean} `true` if the string is a valid IPv6 address, otherwise `false`. + * + * @example + * isIPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); // true + * + * @example + * isIPv6("2001:db8:85a3::8a2e:370:7334"); // true (uses shorthand) + * + * @example + * isIPv6("2001:db8:::1"); // false (invalid use of shorthand) + * + * @example + * isIPv6("12345::abcd"); // false (group too long) + */ +export function isIPv6(str: string): boolean { + const lower = str.toLowerCase(); + + if (!/^[0-9a-f:]+$/.test(lower)) return false; + + const parts = lower.split("::"); + if (parts.length > 2) return false; + + const left = parts[0] ? parts[0].split(":") : []; + const right = parts[1] ? parts[1].split(":") : []; + + // Each part (excluding shorthand) must be 1–4 hex digits + const validGroup = (g: string) => /^[0-9a-f]{1,4}$/.test(g); + + if (!left.every(validGroup) || !right.every(validGroup)) return false; + + const totalGroups = left.length + right.length; + if (parts.length === 1) + return totalGroups === 8; + else + return totalGroups < 8; +}