Skip to content

Commit 054eedb

Browse files
Added assert.string.pattern()
1 parent f8ee74e commit 054eedb

File tree

4 files changed

+157
-1
lines changed

4 files changed

+157
-1
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,40 @@ assert.string.length("John Doe", 5); // ❌ Invalid value: "John Doe".
363363
```
364364

365365

366+
### `assert.string.pattern(value, regex, [fieldName], [defaultValue])`
367+
Asserts that a value is a string that matches the specified Regular Expression pattern.
368+
369+
- **value** - The value to check
370+
- **regex** - The regular expression to test
371+
- **fieldName** - (optional) The name of the field being assertd. This is used in the error message if the assertion fails.
372+
- **defaultValue** - (optional) The default value to use if `value` is `undefined`.
373+
374+
```javascript
375+
import assert from "@jsdevtools/assert";
376+
377+
assert.string.pattern("Foo", /^fo+$/i); //
378+
assert.string.pattern("image.jpg", /\.(jpg|jpeg)/); //
379+
assert.string.pattern("", /^\w*$/); //
380+
381+
assert.string.pattern("foobar", /^fo+$/i); // ❌ Invalid value: "foobar". It must match /^fo+$/i.
382+
assert.string.pattern("jpeg.gif", /\.(jpg|jpeg)/); // ❌ Invalid value: "jpeg.gif". It must match /\\.(jpg|jpeg)/.
383+
```
384+
385+
You can customize the assertion error message by adding an `example` or `examples` property to the RegExp object.
386+
387+
```javascript
388+
import assert from "@jsdevtools/assert";
389+
390+
let pattern = /\.(jpg|jpeg)/;
391+
pattern.example = "image.jpg";
392+
assert.string.pattern("jpeg.gif", pattern); // ❌ Invalid value: "jpeg.gif". It should look like "image.jpg".
393+
394+
let pattern = /\.(jpg|jpeg)/;
395+
pattern.examples = ["image.jpg", "image.jpeg"];
396+
assert.string.pattern("jpeg.gif", pattern); // ❌ Invalid value: "jpeg.gif". It should look like "image.jpg" or "image.jpeg".
397+
```
398+
399+
366400
### `assert.string.enum(value, enum, [fieldName], [defaultValue])`
367401
Asserts that a value is a string that is a member of the specified enumeration object.
368402

src/string.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ export interface AssertString {
8181
* Asserts that a value is one of the values of an enumeration object.
8282
*/
8383
enum(value: unknown, enumeration: object, fieldName?: string, defaultValue?: string): string;
84+
85+
/**
86+
* Asserts that a value matches the specified regular expression pattern.
87+
*/
88+
pattern<T extends string>(value: T | undefined, pattern: RegExp, fieldName?: string, defaultValue?: T): T;
89+
90+
/**
91+
* Asserts that a value matches the specified regular expression pattern.
92+
*/
93+
pattern(value: unknown, pattern: RegExp, fieldName?: string, defaultValue?: unknown): string;
8494
}
8595

8696

@@ -95,6 +105,7 @@ string.minLength = assertMinLength;
95105
string.maxLength = assertMaxLength;
96106
Object.defineProperty(string, "length", { value: assertLength });
97107
string.enum = assertEnum;
108+
string.pattern = assertPattern;
98109

99110

100111
function assertNonEmpty(value: string | undefined, fieldName = "value", defaultValue?: string): string {
@@ -183,3 +194,29 @@ value: string | undefined, enumeration: object, fieldName = "value", defaultValu
183194

184195
return value;
185196
}
197+
198+
declare global {
199+
interface RegExp {
200+
example?: string;
201+
examples?: string[];
202+
}
203+
}
204+
205+
function assertPattern(value: string | undefined, pattern: RegExp, fieldName = "value", defaultValue?: string): string {
206+
value = type.string(value, fieldName, defaultValue);
207+
208+
if (!pattern.test(value)) {
209+
if (Array.isArray(pattern.examples)) {
210+
let humanizedExamples = humanize.values(pattern.examples, { conjunction: "or" });
211+
throw ono(`Invalid ${fieldName}: ${humanize(value)}. It should look like ${humanizedExamples}.`);
212+
}
213+
if (typeof pattern.example === "string") {
214+
throw ono(`Invalid ${fieldName}: ${humanize(value)}. It should look like ${humanize(pattern.example)}.`);
215+
}
216+
else {
217+
throw ono(`Invalid ${fieldName}: ${humanize(value)}. It must match ${pattern}.`);
218+
}
219+
}
220+
221+
return value;
222+
}

test/specs/string-enum.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe("assert.string.enum()", () => {
1111
expect(assert.string.enum("", { None: "", All: "all" })).to.equal("");
1212
});
1313

14-
it("should throw an error for values that are not in the list of allowed values", () => {
14+
it("should throw an error for values that are not in the enumeration", () => {
1515
function notAllowed (value, enumeration) {
1616
return () => {
1717
assert.string.enum(value, enumeration);

test/specs/string-pattern.spec.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"use strict";
2+
3+
const { assert } = require("../../lib");
4+
const { expect } = require("chai");
5+
6+
describe("assert.string.pattern()", () => {
7+
8+
it("should assert values that match the pattern", () => {
9+
expect(assert.string.pattern("", /^$/)).to.equal("");
10+
expect(assert.string.pattern("FoOo", /^fo+$/i)).to.equal("FoOo");
11+
expect(assert.string.pattern("logo.jpg", /\.(jpg|jpeg)$/)).to.equal("logo.jpg");
12+
});
13+
14+
it("should throw an error for values that do not match the pattern", () => {
15+
function invalid (value, pattern) {
16+
return () => {
17+
assert.string.pattern(value, pattern);
18+
};
19+
}
20+
21+
expect(invalid("foobar", /^fo+$/i))
22+
.to.throw(Error, 'Invalid value: "foobar". It must match /^fo+$/i.');
23+
24+
expect(invalid("jpeg.gif", /\.(jpg|jpeg)$/))
25+
.to.throw(Error, 'Invalid value: "jpeg.gif". It must match /\\.(jpg|jpeg)$/.');
26+
27+
expect(invalid("", /^a-z+$/))
28+
.to.throw(Error, 'Invalid value: "". It must match /^a-z+$/.');
29+
});
30+
31+
it("should throw an error for invalid defaults", () => {
32+
function badDefault (defaultValue, pattern) {
33+
return () => {
34+
assert.string.pattern(undefined, pattern, "thing", defaultValue);
35+
};
36+
}
37+
38+
expect(badDefault("foobar", /^fo+$/i))
39+
.to.throw(Error, 'Invalid thing: "foobar". It must match /^fo+$/i.');
40+
41+
expect(badDefault("jpeg.gif", /\.(jpg|jpeg)$/))
42+
.to.throw(Error, 'Invalid thing: "jpeg.gif". It must match /\\.(jpg|jpeg)$/.');
43+
44+
expect(badDefault("", /^a-z+$/))
45+
.to.throw(Error, 'Invalid thing: "". It must match /^a-z+$/.');
46+
});
47+
48+
it("should throw an error using the example", () => {
49+
function invalidWithExample (value, pattern, example) {
50+
return () => {
51+
pattern.examples = example;
52+
pattern.example = example;
53+
assert.string.pattern(value, pattern);
54+
};
55+
}
56+
57+
expect(invalidWithExample("foobar", /^fo+$/i, "fooooo"))
58+
.to.throw(Error, 'Invalid value: "foobar". It should look like "fooooo".');
59+
60+
expect(invalidWithExample("jpeg.gif", /\.(jpg|jpeg)$/, ["image.jpg", "image.jpeg"]))
61+
.to.throw(Error, 'Invalid value: "jpeg.gif". It should look like "image.jpg" or "image.jpeg".');
62+
63+
expect(invalidWithExample("", /^a-z$/, ["a", "b", "c", "d", "e"]))
64+
.to.throw(Error, 'Invalid value: "". It should look like "a", "b", "c", "d", or "e".');
65+
});
66+
67+
it("should not use the example if it's not a string", () => {
68+
function invalidExample (value, pattern, example) {
69+
return () => {
70+
pattern.example = example;
71+
assert.string.pattern(value, pattern);
72+
};
73+
}
74+
75+
expect(invalidExample("foobar", /^fo+$/i, 12345))
76+
.to.throw(Error, 'Invalid value: "foobar". It must match /^fo+$/i.');
77+
78+
expect(invalidExample("jpeg.gif", /\.(jpg|jpeg)$/, new Date()))
79+
.to.throw(Error, 'Invalid value: "jpeg.gif". It must match /\\.(jpg|jpeg)$/.');
80+
81+
expect(invalidExample("", /^a-z+$/, String))
82+
.to.throw(Error, 'Invalid value: "". It must match /^a-z+$/.');
83+
});
84+
85+
});

0 commit comments

Comments
 (0)