diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 6240e15..3ea318a 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -107,3 +107,26 @@ export function safeValidateIdentifier( } return { success: false, error: result.error.issues[0]?.message ?? 'Validation failed' }; } + +/** + * Validation schema for Tangled-specific DID (did:plc: format only) + */ +export const tangledDidSchema = z + .string() + .regex(/^did:plc:[a-z0-9]+$/, 'Invalid Tangled DID format. Expected: did:plc:...'); + +/** + * Check if a string is a valid AT Protocol handle + * Returns true/false without throwing + */ +export function isValidHandle(handle: string): boolean { + return handleSchema.safeParse(handle).success; +} + +/** + * Check if a string is a valid Tangled DID (did:plc: format) + * Returns true/false without throwing + */ +export function isValidTangledDid(did: string): boolean { + return tangledDidSchema.safeParse(did).success; +} diff --git a/tests/utils/validation.test.ts b/tests/utils/validation.test.ts index 05379f7..aa6eef5 100644 --- a/tests/utils/validation.test.ts +++ b/tests/utils/validation.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from 'vitest'; import { + isValidHandle, + isValidTangledDid, safeValidateDid, safeValidateHandle, safeValidateIdentifier, @@ -136,3 +138,36 @@ describe('App Password Validation', () => { }); }); }); + +describe('Boolean Validation Helpers', () => { + describe('isValidHandle', () => { + it('should return true for valid handles', () => { + expect(isValidHandle('user.bsky.social')).toBe(true); + expect(isValidHandle('markbennett.ca')).toBe(true); + expect(isValidHandle('sub.domain.example.com')).toBe(true); + }); + + it('should return false for invalid handles', () => { + expect(isValidHandle('invalid')).toBe(false); + expect(isValidHandle('.starts-with-dot.com')).toBe(false); + expect(isValidHandle('ends-with-dot.com.')).toBe(false); + expect(isValidHandle('has space.com')).toBe(false); + expect(isValidHandle('')).toBe(false); + }); + }); + + describe('isValidTangledDid', () => { + it('should return true for valid Tangled DIDs (did:plc: format)', () => { + expect(isValidTangledDid('did:plc:b2mcbcamkwyznc5fkplwlxbf')).toBe(true); + expect(isValidTangledDid('did:plc:abc123xyz')).toBe(true); + }); + + it('should return false for invalid Tangled DIDs', () => { + expect(isValidTangledDid('did:plc:')).toBe(false); + expect(isValidTangledDid('did:plc:ABC123')).toBe(false); // uppercase not allowed + expect(isValidTangledDid('did:web:example.com')).toBe(false); // wrong method + expect(isValidTangledDid('not-a-did')).toBe(false); + expect(isValidTangledDid('')).toBe(false); + }); + }); +});