Skip to content
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -725,6 +726,25 @@ isIPv4('192.168.1.a'); // false (non-numeric)
| --------- | ------ | -------- | -------------------------------------------- |
| text | string | required | The input string to validate as IPv4 address |

#### <a id="isipv6"></a>`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 |

#### <a id="ishexcolor"></a>`isHexColor(text)`

Checks if a string is a valid Hex color.
Expand Down
71 changes: 71 additions & 0 deletions src/tests/validations/isIPv6.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
3 changes: 3 additions & 0 deletions src/validations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -40,6 +42,7 @@ export const validations = {
isSlug,
isURL,
isIPv4,
isIPv6,
isHexColor,
isPalindrome,
isLowerCase,
Expand Down
45 changes: 45 additions & 0 deletions src/validations/isIPv6.ts
Original file line number Diff line number Diff line change
@@ -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;
}