Skip to content

Commit

Permalink
feat: improve SSN validation error messages for clarity tckt-364
Browse files Browse the repository at this point in the history
  • Loading branch information
kalasgarov committed Nov 22, 2024
1 parent 7eaa3b1 commit 8898cc1
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('SocialSecurityNumberPattern tests', () => {
const invalidResult = schema.safeParse(invalidInput);
expect(invalidResult.success).toBe(false);
expect(invalidResult.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
'Social Security Number must have exactly 9 digits'
);
});

Expand All @@ -43,7 +43,7 @@ describe('SocialSecurityNumberPattern tests', () => {
const invalidResult = schema.safeParse(invalidInput);
expect(invalidResult.success).toBe(false);
expect(invalidResult.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
'Social Security Number must have exactly 9 digits'
);
});

Expand All @@ -59,7 +59,7 @@ describe('SocialSecurityNumberPattern tests', () => {
const shortInputResult = schema.safeParse(shortInput);
expect(shortInputResult.success).toBe(false);
expect(shortInputResult.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
'Social Security Number must have exactly 9 digits'
);
});

Expand All @@ -76,7 +76,7 @@ describe('SocialSecurityNumberPattern tests', () => {
const result = schema.safeParse(ssn);
expect(result.success).toBe(false);
expect(result.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
'Social Security Number must start with a valid prefix (not 9, 666, or 000)'
);
});
});
Expand All @@ -93,9 +93,16 @@ describe('SocialSecurityNumberPattern tests', () => {
invalidSSNs.forEach(ssn => {
const result = schema.safeParse(ssn);
expect(result.success).toBe(false);
expect(result.error?.issues[0].message).toBe(
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
);
const errorMessage = result.error?.issues[0].message;
if (ssn === '555-00-6789') {
expect(errorMessage).toBe(
'Social Security Number must have a valid middle segment (not 00)'
);
} else if (ssn === '555-12-0000') {
expect(errorMessage).toBe(
'Social Security Number must have a valid suffix (not 0000)'
);
}
});
});
});
Expand Down Expand Up @@ -147,7 +154,7 @@ describe('SocialSecurityNumberPattern tests', () => {
if (!result.success) {
expect(result.error).toBeDefined();
expect(result.error?.message).toContain(
'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria'
'Social Security Number must have exactly 9 digits'
);
} else {
expect.fail('Unexpected validation success');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,68 @@ export type SocialSecurityNumberPatternOutput = z.infer<
>;

export const createSSNSchema = (data: SocialSecurityNumberPattern['data']) => {
const ssnSchema = z.string().refine(value => {
const stripped = value.replace(/[^0-9]/g, '');

const isValidFormat = /^\d{3}-\d{2}-\d{4}$|^\d{9}$/.test(value);

if (!isValidFormat) {
return false;
}

if (stripped.length !== 9) {
return false;
}

const hasValidPrefix = !(
stripped.startsWith('9') ||
stripped.startsWith('666') ||
stripped.startsWith('000')
);

const hasValidMiddle = stripped.slice(3, 5) !== '00';
const hasValidSuffix = stripped.slice(5) !== '0000';

return hasValidPrefix && hasValidMiddle && hasValidSuffix;
}, 'Social Security Number must contain exactly 9 digits, be formatted as XXX-XX-XXXX or XXXXXXXXX, and meet SSA issuance criteria');

if (!data.required) {
return ssnSchema.or(z.literal('').optional()).optional();
const baseSchema = z
.string()
.transform(value => value.replace(/[^0-9]/g, ''))
.superRefine((value, ctx) => {
if (!data.required && value === '') {
return;
}

let issues = [];

if (value.length !== 9) {
issues.push('have exactly 9 digits');
} else {
if (
value.startsWith('9') ||
value.startsWith('666') ||
value.startsWith('000')
) {
issues.push('start with a valid prefix (not 9, 666, or 000)');
}

if (value.slice(3, 5) === '00') {
issues.push('have a valid middle segment (not 00)');
}

if (value.slice(5) === '0000') {
issues.push('have a valid suffix (not 0000)');
}
}

if (issues.length > 0) {
let enhancedMessage = 'Social Security Number must ';
if (issues.length === 1) {
enhancedMessage += issues[0];
} else if (issues.length === 2) {
enhancedMessage += `${issues[0]} and ${issues[1]}`;
} else {
enhancedMessage += `${issues.slice(0, -1).join(', ')}, and ${issues[issues.length - 1]}`;
}

ctx.addIssue({
code: z.ZodIssueCode.custom,
message: enhancedMessage,
});
}
});

if (data.required) {
return z
.string()
.refine(value => value.trim().length > 0, {
message: 'This field is required',
})
.superRefine((value, ctx) => {
const result = baseSchema.safeParse(value.trim());
if (!result.success) {
result.error.issues.forEach(issue => ctx.addIssue(issue));
}
});
} else {
return baseSchema.optional();
}

return ssnSchema;
};

export const socialSecurityNumberConfig: PatternConfig<
Expand Down

0 comments on commit 8898cc1

Please sign in to comment.