From 6e2377570aa03503b35566ae31c03748bacd30a7 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Fri, 31 Jul 2026 09:56:51 -0700 Subject: [PATCH 1/3] Simplify format checks --- src/format.spec.ts | 29 ++++++++++------------------- src/index.ts | 28 +++++++++++----------------- 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/src/format.spec.ts b/src/format.spec.ts index a613e76..88721c3 100644 --- a/src/format.spec.ts +++ b/src/format.spec.ts @@ -5,10 +5,7 @@ describe('format(credentials)', function () { describe('arguments', function () { describe('credentials', function () { it('should be required', function () { - assert.throws( - () => (format as any)(), - /argument credentials is required/, - ); + assert.throws(() => (format as any)(), /credentials is required/); }); it('should accept credentials', function () { @@ -19,63 +16,57 @@ describe('format(credentials)', function () { it('should reject null', function () { assert.throws( format.bind(null, null as any), - /argument credentials is required/, + /credentials is required/, ); }); it('should reject a number', function () { - assert.throws( - format.bind(null, 42 as any), - /argument credentials is required/, - ); + assert.throws(format.bind(null, 42 as any), /credentials is required/); }); it('should reject a string', function () { - assert.throws( - format.bind(null, '' as any), - /argument credentials is required/, - ); + assert.throws(format.bind(null, '' as any), /credentials is required/); }); it('should reject an object without name', function () { assert.throws( format.bind(null, { pass: 'bar' } as any), - /argument credentials is required to have name and pass properties/, + /credentials is required to have name and pass properties/, ); }); it('should reject an object without pass', function () { assert.throws( format.bind(null, { name: 'foo' } as any), - /argument credentials is required to have name and pass properties/, + /credentials is required to have name and pass properties/, ); }); it('should reject an object with non-string name', function () { assert.throws( format.bind(null, { name: 42, pass: 'bar' } as any), - /argument credentials is required to have name and pass properties/, + /credentials is required to have name and pass properties/, ); }); it('should reject an object with non-string pass', function () { assert.throws( format.bind(null, { name: 'foo', pass: 42 } as any), - /argument credentials is required to have name and pass properties/, + /credentials is required to have name and pass properties/, ); }); it('should reject userid containing colon', function () { assert.throws( format.bind(null, { name: 'foo:bar', pass: 'baz' }), - /must not contain a colon or control characters/, + /must not contain a colon/, ); }); it('should reject control chars in userid', function () { assert.throws( format.bind(null, { name: 'foo\u0000bar', pass: 'baz' }), - /must not contain a colon or control characters/, + /must not contain control characters/, ); }); diff --git a/src/index.ts b/src/index.ts index d4d4b1b..6e5a5fd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,12 +52,8 @@ export function parse(string: string): Credentials | undefined { * @public */ export function format(credentials: Credentials): string { - if (!credentials) { - throw new TypeError('argument credentials is required'); - } - - if (typeof credentials !== 'object') { - throw new TypeError('argument credentials is required to be an object'); + if (typeof credentials !== 'object' || credentials === null) { + throw new TypeError('credentials is required to be an object'); } if ( @@ -65,26 +61,24 @@ export function format(credentials: Credentials): string { typeof credentials.pass !== 'string' ) { throw new TypeError( - 'argument credentials is required to have name and pass properties', + 'credentials is required to have name and pass properties', ); } - if ( - credentials.name.includes(':') || // RFC 7617 disallows colon in username - CONTROL_CHARS_REGEXP.test(credentials.name) - ) { - throw new TypeError( - 'argument credentials.name must not contain a colon or control characters', - ); + // RFC 7617 disallows colon in username + if (credentials.name.includes(':')) { + throw new TypeError('name must not contain a colon'); } - if (CONTROL_CHARS_REGEXP.test(credentials.pass)) { + const str = credentials.name + ':' + credentials.pass; + + if (CONTROL_CHARS_REGEXP.test(str)) { throw new TypeError( - 'argument credentials.pass must not contain control characters', + 'argument credentials must not contain control characters', ); } - return 'Basic ' + base64.encode(credentials.name + ':' + credentials.pass); + return 'Basic ' + base64.encode(str); } /** From 2db808b667cb7dfca1ff6d1e6ab338c6a55a29c4 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Mon, 3 Aug 2026 08:48:45 -0700 Subject: [PATCH 2/3] Update error messages --- src/format.spec.ts | 31 ++++++++++++++++++++----------- src/index.ts | 8 ++++---- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/format.spec.ts b/src/format.spec.ts index 88721c3..2890c97 100644 --- a/src/format.spec.ts +++ b/src/format.spec.ts @@ -5,7 +5,10 @@ describe('format(credentials)', function () { describe('arguments', function () { describe('credentials', function () { it('should be required', function () { - assert.throws(() => (format as any)(), /credentials is required/); + assert.throws( + () => (format as any)(), + /"credentials" must be an object/, + ); }); it('should accept credentials', function () { @@ -16,43 +19,49 @@ describe('format(credentials)', function () { it('should reject null', function () { assert.throws( format.bind(null, null as any), - /credentials is required/, + /"credentials" must be an object/, ); }); it('should reject a number', function () { - assert.throws(format.bind(null, 42 as any), /credentials is required/); + assert.throws( + format.bind(null, 42 as any), + /"credentials" must be an object/, + ); }); it('should reject a string', function () { - assert.throws(format.bind(null, '' as any), /credentials is required/); + assert.throws( + format.bind(null, '' as any), + /"credentials" must be an object/, + ); }); it('should reject an object without name', function () { assert.throws( format.bind(null, { pass: 'bar' } as any), - /credentials is required to have name and pass properties/, + /"credentials" must have string properties "name" and "pass"/, ); }); it('should reject an object without pass', function () { assert.throws( format.bind(null, { name: 'foo' } as any), - /credentials is required to have name and pass properties/, + /"credentials" must have string properties "name" and "pass"/, ); }); it('should reject an object with non-string name', function () { assert.throws( format.bind(null, { name: 42, pass: 'bar' } as any), - /credentials is required to have name and pass properties/, + /"credentials" must have string properties "name" and "pass"/, ); }); it('should reject an object with non-string pass', function () { assert.throws( format.bind(null, { name: 'foo', pass: 42 } as any), - /credentials is required to have name and pass properties/, + /"credentials" must have string properties "name" and "pass"/, ); }); @@ -87,21 +96,21 @@ describe('format(credentials)', function () { }); describe('with empty password', function () { - it('should throw', function () { + it('should return header', function () { const header = format({ name: 'foo', pass: '' }); assert.strictEqual(header, 'Basic Zm9vOg=='); }); }); describe('with empty userid', function () { - it('should throw', function () { + it('should return header', function () { const header = format({ name: '', pass: 'pass' }); assert.strictEqual(header, 'Basic OnBhc3M='); }); }); describe('with empty userid and pass', function () { - it('should throw', function () { + it('should return header', function () { const header = format({ name: '', pass: '' }); assert.strictEqual(header, 'Basic Og=='); }); diff --git a/src/index.ts b/src/index.ts index 6e5a5fd..baeb90a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,7 +53,7 @@ export function parse(string: string): Credentials | undefined { */ export function format(credentials: Credentials): string { if (typeof credentials !== 'object' || credentials === null) { - throw new TypeError('credentials is required to be an object'); + throw new TypeError('"credentials" must be an object'); } if ( @@ -61,20 +61,20 @@ export function format(credentials: Credentials): string { typeof credentials.pass !== 'string' ) { throw new TypeError( - 'credentials is required to have name and pass properties', + '"credentials" must have string properties "name" and "pass"', ); } // RFC 7617 disallows colon in username if (credentials.name.includes(':')) { - throw new TypeError('name must not contain a colon'); + throw new TypeError('"name" must not contain a colon'); } const str = credentials.name + ':' + credentials.pass; if (CONTROL_CHARS_REGEXP.test(str)) { throw new TypeError( - 'argument credentials must not contain control characters', + '"name" and "pass" must not contain control characters', ); } From b57ad903d857974375fb73be00019395e49466f8 Mon Sep 17 00:00:00 2001 From: Blake Embrey Date: Mon, 3 Aug 2026 09:04:56 -0700 Subject: [PATCH 3/3] Update error format --- src/format.spec.ts | 28 ++++++++-------------------- src/index.ts | 12 +++++------- src/parse.spec.ts | 6 +++--- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/src/format.spec.ts b/src/format.spec.ts index 2890c97..e64c0a9 100644 --- a/src/format.spec.ts +++ b/src/format.spec.ts @@ -5,10 +5,7 @@ describe('format(credentials)', function () { describe('arguments', function () { describe('credentials', function () { it('should be required', function () { - assert.throws( - () => (format as any)(), - /"credentials" must be an object/, - ); + assert.throws(() => (format as any)(), /Expected an object/); }); it('should accept credentials', function () { @@ -17,51 +14,42 @@ describe('format(credentials)', function () { }); it('should reject null', function () { - assert.throws( - format.bind(null, null as any), - /"credentials" must be an object/, - ); + assert.throws(format.bind(null, null as any), /Expected an object/); }); it('should reject a number', function () { - assert.throws( - format.bind(null, 42 as any), - /"credentials" must be an object/, - ); + assert.throws(format.bind(null, 42 as any), /Expected an object/); }); it('should reject a string', function () { - assert.throws( - format.bind(null, '' as any), - /"credentials" must be an object/, - ); + assert.throws(format.bind(null, '' as any), /Expected an object/); }); it('should reject an object without name', function () { assert.throws( format.bind(null, { pass: 'bar' } as any), - /"credentials" must have string properties "name" and "pass"/, + /Object must have string properties "name" and "pass"/, ); }); it('should reject an object without pass', function () { assert.throws( format.bind(null, { name: 'foo' } as any), - /"credentials" must have string properties "name" and "pass"/, + /Object must have string properties "name" and "pass"/, ); }); it('should reject an object with non-string name', function () { assert.throws( format.bind(null, { name: 42, pass: 'bar' } as any), - /"credentials" must have string properties "name" and "pass"/, + /Object must have string properties "name" and "pass"/, ); }); it('should reject an object with non-string pass', function () { assert.throws( format.bind(null, { name: 'foo', pass: 42 } as any), - /"credentials" must have string properties "name" and "pass"/, + /Object must have string properties "name" and "pass"/, ); }); diff --git a/src/index.ts b/src/index.ts index baeb90a..959b832 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,7 +26,7 @@ export interface Credentials { export function parse(string: string): Credentials | undefined { if (typeof string !== 'string') { - return undefined; + throw new TypeError('Expected a string'); } // parse header @@ -53,28 +53,26 @@ export function parse(string: string): Credentials | undefined { */ export function format(credentials: Credentials): string { if (typeof credentials !== 'object' || credentials === null) { - throw new TypeError('"credentials" must be an object'); + throw new TypeError('Expected an object'); } if ( typeof credentials.name !== 'string' || typeof credentials.pass !== 'string' ) { - throw new TypeError( - '"credentials" must have string properties "name" and "pass"', - ); + throw new TypeError('Object must have string properties "name" and "pass"'); } // RFC 7617 disallows colon in username if (credentials.name.includes(':')) { - throw new TypeError('"name" must not contain a colon'); + throw new TypeError('Object "name" must not contain a colon'); } const str = credentials.name + ':' + credentials.pass; if (CONTROL_CHARS_REGEXP.test(str)) { throw new TypeError( - '"name" and "pass" must not contain control characters', + 'Object "name" and "pass" must not contain control characters', ); } diff --git a/src/parse.spec.ts b/src/parse.spec.ts index 6347ea1..5bd5b79 100644 --- a/src/parse.spec.ts +++ b/src/parse.spec.ts @@ -2,9 +2,9 @@ import { describe, it, assert } from 'vitest'; import { parse } from './index.js'; describe('parse(string)', function () { - describe('with undefined string', function () { - it('should return undefined', function () { - assert.strictEqual((parse as any)(), undefined); + describe('with non string', function () { + it('should throw', function () { + assert.throws(() => parse(undefined as any), /Expected a string/); }); });