Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds regexRuleConstructor to close #1978 #1980

Merged
merged 1 commit into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading