Skip to content

Commit

Permalink
feat: Adds regexRuleConstructor to close #1978
Browse files Browse the repository at this point in the history
  • Loading branch information
ryasmi committed Dec 18, 2024
1 parent 1bef9c3 commit abc1dad
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 5 deletions.
16 changes: 11 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -105,14 +105,20 @@ 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)
- [sanitizeNumberFromString](./src/sanitizationRules/sanitizeNumberFromString/readme.md)
- [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)
Expand Down
38 changes: 38 additions & 0 deletions src/ruleConstructors/regexRuleConstructor/readme.md
Original file line number Diff line number Diff line change
@@ -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<typeof constrainToExample>
// {
// example: rulr.Constrained<typeof abcSymbol, string>
// }

// Valid
const example1: Example = constrainToExample({
example: 'a',
})

// Valid
const example2: Example = constrainToExample({
example: 1,
})

// Invalid
const example3: Example = constrainToExample({
example: '1',
})
```
Original file line number Diff line number Diff line change
@@ -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<typeof ruleSymbol, string> = 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()
}
}
})
32 changes: 32 additions & 0 deletions src/ruleConstructors/regexRuleConstructor/regexRuleConstructor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { BaseError } from 'make-error';
import { Constrained, Rule } from '../../core'
import { isString } from '../../valueRules/string/string'

type Result<T extends symbol> = [
Rule<Constrained<T, string>>,
typeof BaseError,
(input: unknown) => input is Constrained<T, string>
]

export function regexRuleConstructor<T extends symbol>(regex: RegExp, symbol: T, ruleName = 'valid value'): Result<T> {
type RegexString = Constrained<typeof symbol, string>

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]
}
1 change: 1 addition & 0 deletions src/rulr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit abc1dad

Please sign in to comment.