From 551df615b0f100b1f254a73bd918e47da6de5707 Mon Sep 17 00:00:00 2001 From: Ryan Smith <3045513+ryasmi@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:10:12 +0000 Subject: [PATCH] feat: Adds regexRuleConstructor to close #1978 (#1980) --- readme.md | 16 ++++--- .../regexRuleConstructor/readme.md | 38 +++++++++++++++++ .../regexRuleConstructor.test.ts | 42 +++++++++++++++++++ .../regexRuleConstructor.ts | 32 ++++++++++++++ src/rulr.ts | 1 + 5 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 src/ruleConstructors/regexRuleConstructor/readme.md create mode 100644 src/ruleConstructors/regexRuleConstructor/regexRuleConstructor.test.ts create mode 100644 src/ruleConstructors/regexRuleConstructor/regexRuleConstructor.ts diff --git a/readme.md b/readme.md index 5dead50b..ba53eb1e 100644 --- a/readme.md +++ b/readme.md @@ -50,8 +50,8 @@ To save you some time, Rulr comes with the following rules. - [array](./src/higherOrderRules/array/readme.md) - [bigint](./src/valueRules/bigint/readme.md) - [boolean](./src/valueRules/boolean/readme.md) - - [trueRule](./src/valueRules/trueRule/readme.md) - - [falseRule](./src/valueRules/falseRule/readme.md) + - [trueRule](./src/valueRules/trueRule/readme.md) + - [falseRule](./src/valueRules/falseRule/readme.md) - [constant](./src/valueRules/constant/readme.md) - [date](./src/valueRules/date/readme.md) - [dictionary](./src/higherOrderRules/dictionary/readme.md) @@ -76,7 +76,7 @@ Since it's quite common to want to restrict the size of strings to avoid UI over ### Constraining Strings -Rulr also comes with a growing list of convenient rules for constraining strings that are mostly built on [Chris O'Hara's extensive and much loved validator package](https://www.npmjs.com/package/validator). As with the rules for sized strings above, this can help prevent UI overflow bugs, DB storage errors, and processing errors. +Rulr comes with a growing list of convenient rules for constraining strings that are mostly built on [Chris O'Hara's extensive and much loved validator package](https://www.npmjs.com/package/validator). As with the rules for sized strings above, this can help prevent UI overflow bugs, DB storage errors, and processing errors. - [email](./src/constrainedStrings/email/readme.md) - [iri](./src/constrainedStrings/iri/readme.md) @@ -95,7 +95,7 @@ Rulr also comes with a growing list of convenient rules for constraining strings ### Constraining Non-Strings -In addition to the constrained strings, Rulr also comes with a few convenient rules to help you quickly validate non-string values. +In addition to the constrained strings, Rulr comes with a few convenient rules to help you quickly validate non-string values. - [integer](./src/constrainedValues/integer/readme.md) - [negativeInteger](./src/constrainedValues/negativeInteger/readme.md) @@ -105,7 +105,7 @@ In addition to the constrained strings, Rulr also comes with a few convenient ru ### Sanitization Rules -Finally, Rulr is starting to provide rules that sanitize inputs from HTTP headers and URL params. +Rulr provides rules that sanitize inputs from HTTP headers and URL params. - [sanitizeBooleanFromString](./src/sanitizationRules/sanitizeBooleanFromString/readme.md) - [sanitizeJsonFromString](./src/sanitizationRules/sanitizeJsonFromString/readme.md) @@ -113,6 +113,12 @@ Finally, Rulr is starting to provide rules that sanitize inputs from HTTP header - [sanitizeBasicAuthFromString](./src/sanitizationRules/sanitizeBasicAuthFromString/readme.md) - [sanitizeJWTBearerAuthFromString](./src/sanitizationRules/sanitizeJWTBearerAuthFromString/readme.md) +### Rule Constructors + +Finally, Rulr is starting to provide rule constructors that allow you quickly make your own rules. + +- [regexRuleConstructor](./src/ruleConstructors/regexRuleConstructor/readme.md) + ### Frequently Awesome Questions 🤘 - [How does Rulr protect against unit conversion errors?](./docs/unitConversionErrorProtection.md) diff --git a/src/ruleConstructors/regexRuleConstructor/readme.md b/src/ruleConstructors/regexRuleConstructor/readme.md new file mode 100644 index 00000000..ef9156c1 --- /dev/null +++ b/src/ruleConstructors/regexRuleConstructor/readme.md @@ -0,0 +1,38 @@ +# regexRuleConstructor + +[Back to root readme.md](../../../readme.md) + +This function can be used to construct rules that ensure an input matches some given regex as shown in the example below. The function also generates an error class and a guard function. The rule should only throw errors with the generated error class. + +```ts +import * as rulr from 'rulr' + +const abcSymbol = Symbol() +const [abc, AbcError, abcGuard] = regexRuleConstructor(/^[abc]$/, abcSymbol) + +const constrainToExample = rulr.object({ + required: { + example: abc, + }, +}) + +type Example = rulr.Static +// { +// example: rulr.Constrained +// } + +// Valid +const example1: Example = constrainToExample({ + example: 'a', +}) + +// Valid +const example2: Example = constrainToExample({ + example: 1, +}) + +// Invalid +const example3: Example = constrainToExample({ + example: '1', +}) +``` diff --git a/src/ruleConstructors/regexRuleConstructor/regexRuleConstructor.test.ts b/src/ruleConstructors/regexRuleConstructor/regexRuleConstructor.test.ts new file mode 100644 index 00000000..4ef0689b --- /dev/null +++ b/src/ruleConstructors/regexRuleConstructor/regexRuleConstructor.test.ts @@ -0,0 +1,42 @@ +import * as assert from 'assert' +import { regexRuleConstructor, Constrained } from '../../rulr' + +const ruleSymbol = Symbol() +const [rule, InvalidValueError, guard] = regexRuleConstructor(/^[abc]$/, ruleSymbol) + +test('regexRuleConstructor rule should allow a matching string', () => { + const input = 'a' + const output: Constrained = rule(input) + assert.equal(guard(input), true) + assert.strictEqual(output, input) + assert.ok(InvalidValueError) +}) + +test('regexRuleConstructor rule should not allow a non-string value', () => { + const input = 1 + assert.equal(guard(input), false) + assert.throws(() => rule(input), InvalidValueError) +}) + +test('regexRuleConstructor rule should not allow a non-matching value', () => { + const input = '1' + assert.equal(guard(input), false) + assert.throws(() => rule(input), InvalidValueError) +}) + +test('regexRuleConstructor rule should not allow a non-matching value', () => { + const ruleSymbol = Symbol() + const ruleName = 'test value' + const [rule, InvalidValueError] = regexRuleConstructor(/^[abc]$/, ruleSymbol, ruleName) + const input = '1' + try { + rule(input) + assert.fail() + } catch (err) { + if (err instanceof InvalidValueError) { + assert.equal(err.message, `expected ${ruleName}`) + } else { + assert.fail() + } + } +}) diff --git a/src/ruleConstructors/regexRuleConstructor/regexRuleConstructor.ts b/src/ruleConstructors/regexRuleConstructor/regexRuleConstructor.ts new file mode 100644 index 00000000..a616da90 --- /dev/null +++ b/src/ruleConstructors/regexRuleConstructor/regexRuleConstructor.ts @@ -0,0 +1,32 @@ +import { BaseError } from 'make-error'; +import { Constrained, Rule } from '../../core' +import { isString } from '../../valueRules/string/string' + +type Result = [ + Rule>, + typeof BaseError, + (input: unknown) => input is Constrained +] + +export function regexRuleConstructor(regex: RegExp, symbol: T, ruleName = 'valid value'): Result { + type RegexString = Constrained + + function guard(input: unknown): input is RegexString { + return isString(input) && regex.test(input) + } + + class InvalidValueError extends BaseError { + constructor() { + super(`expected ${ruleName}`) + } + } + + function rule(input: unknown): RegexString { + if (guard(input)) { + return input + } + throw new InvalidValueError() + } + + return [rule, InvalidValueError, guard] +} diff --git a/src/rulr.ts b/src/rulr.ts index 3650af8e..05684a09 100644 --- a/src/rulr.ts +++ b/src/rulr.ts @@ -88,6 +88,7 @@ export { dictionary, DictionaryKeyValidationError } from './higherOrderRules/dic export { object, InvalidObjectError, PlainObject } from './higherOrderRules/object/object' export { tuple } from './higherOrderRules/tuple/tuple' export { union, UnionValidationError } from './higherOrderRules/union/union' +export { regexRuleConstructor } from './ruleConstructors/regexRuleConstructor/regexRuleConstructor' export { sanitizeBasicAuthFromString, isBasicAuthAsString,