Skip to content

Commit

Permalink
feat: Add sized array rule constructor
Browse files Browse the repository at this point in the history
Fixes #1968

Add `sizedArrayRuleConstructor` to validate array size and items.

* Implement `sizedArrayRuleConstructor` function in `src/ruleConstructors/sizedArrayRuleConstructor/sizedArrayRuleConstructor.ts` to validate array size and items.
* Add tests for `sizedArrayRuleConstructor` in `src/ruleConstructors/sizedArrayRuleConstructor/sizedArrayRuleConstructor.test.ts`.
* Document `sizedArrayRuleConstructor` in `src/ruleConstructors/sizedArrayRuleConstructor/readme.md`.
* Update `readme.md` to include `sizedArrayRuleConstructor` under "Rule Constructors".
* Export `sizedArrayRuleConstructor` in `src/rulr.ts`.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/ryasmi/rulr/issues/1968?shareId=XXXX-XXXX-XXXX-XXXX).
  • Loading branch information
ryasmi committed Jan 9, 2025
1 parent 2ef483f commit 75c6ad9
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 0 deletions.
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Rulr provides rules that sanitize inputs from HTTP headers and URL params.
Finally, Rulr is starting to provide rule constructors that allow you quickly make your own rules.

- [regexRuleConstructor](./src/ruleConstructors/regexRuleConstructor/readme.md)
- [sizedArrayRuleConstructor](./src/ruleConstructors/sizedArrayRuleConstructor/readme.md)

### Frequently Awesome Questions 🤘

Expand Down
43 changes: 43 additions & 0 deletions src/ruleConstructors/sizedArrayRuleConstructor/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# sizedArrayRuleConstructor

[Back to root readme.md](../../../readme.md)

This function can be used to construct rules that ensure an array input has a size within a specified range and that each item in the array satisfies a given rule. 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 exampleSymbol = Symbol()
const [exampleArray, ExampleArrayError, exampleArrayGuard] = rulr.sizedArrayRuleConstructor(rulr.number, 1, 3, exampleSymbol)

const constrainToExample = rulr.object({
required: {
example: exampleArray,
},
})

type Example = rulr.Static<typeof constrainToExample>
// {
// example: rulr.Constrained<typeof exampleSymbol, number[]>
// }

// Valid
const example1: Example = constrainToExample({
example: [1, 2],
})

// Invalid: Array size is less than minSize
const example2: Example = constrainToExample({
example: [],
})

// Invalid: Array size is greater than maxSize
const example3: Example = constrainToExample({
example: [1, 2, 3, 4],
})

// Invalid: Array contains invalid item
const example4: Example = constrainToExample({
example: [1, '2'],
})
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as assert from 'assert'
import { sizedArrayRuleConstructor, Constrained } from '../../rulr'

const ruleSymbol = Symbol()
const [rule, InvalidValueError, guard] = sizedArrayRuleConstructor((input) => input, 1, 3, ruleSymbol)

test('sizedArrayRuleConstructor rule should allow a valid array within size range', () => {
const input = [1, 2]
const output: Constrained<typeof ruleSymbol, unknown[]> = rule(input)
assert.equal(guard(input), true)
assert.deepStrictEqual(output, input)
assert.ok(InvalidValueError)
})

test('sizedArrayRuleConstructor rule should not allow an array smaller than minSize', () => {
const input = []
assert.equal(guard(input), false)
assert.throws(() => rule(input), InvalidValueError)
})

test('sizedArrayRuleConstructor rule should not allow an array larger than maxSize', () => {
const input = [1, 2, 3, 4]
assert.equal(guard(input), false)
assert.throws(() => rule(input), InvalidValueError)
})

test('sizedArrayRuleConstructor rule should not allow an array with invalid items', () => {
const input = [1, '2']
const [rule, InvalidValueError, guard] = sizedArrayRuleConstructor((input) => {
if (typeof input === 'number') {
return input
}
throw new Error('Invalid item')
}, 1, 3, ruleSymbol)
assert.equal(guard(input), false)
assert.throws(() => rule(input), InvalidValueError)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { BaseError } from 'make-error';
import { Constrained, Rule } from '../../core'
import { array, InvalidArrayError } from '../../higherOrderRules/array/array'

Check failure on line 3 in src/ruleConstructors/sizedArrayRuleConstructor/sizedArrayRuleConstructor.ts

View workflow job for this annotation

GitHub Actions / bundle-and-lint

'InvalidArrayError' is defined but never used
import { KeyedValidationError } from '../../errors/KeyedValidationError'

Check failure on line 4 in src/ruleConstructors/sizedArrayRuleConstructor/sizedArrayRuleConstructor.ts

View workflow job for this annotation

GitHub Actions / bundle-and-lint

'KeyedValidationError' is defined but never used
import { ValidationErrors } from '../../errors/ValidationErrors'

Check failure on line 5 in src/ruleConstructors/sizedArrayRuleConstructor/sizedArrayRuleConstructor.ts

View workflow job for this annotation

GitHub Actions / bundle-and-lint

'ValidationErrors' is defined but never used

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

export function sizedArrayRuleConstructor<T extends symbol>(
itemRule: Rule<unknown>,
minSize: number,
maxSize: number,
symbol: T,
ruleName = 'valid array'
): Result<T> {
type SizedArray = Constrained<typeof symbol, unknown[]>

function guard(input: unknown): input is SizedArray {
if (!Array.isArray(input)) {
return false
}
if (input.length < minSize || input.length > maxSize) {
return false
}
try {
array(itemRule)(input)
return true
} catch {
return false
}
}

class InvalidValueError extends BaseError {
constructor() {
super(`expected ${ruleName}`)
}
}

function rule(input: unknown): SizedArray {
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 @@ -89,6 +89,7 @@ export { object, InvalidObjectError, PlainObject } from './higherOrderRules/obje
export { tuple } from './higherOrderRules/tuple/tuple'
export { union, UnionValidationError } from './higherOrderRules/union/union'
export { regexRuleConstructor } from './ruleConstructors/regexRuleConstructor/regexRuleConstructor'
export { sizedArrayRuleConstructor } from './ruleConstructors/sizedArrayRuleConstructor/sizedArrayRuleConstructor'
export {
sanitizeBasicAuthFromString,
isBasicAuthAsString,
Expand Down

0 comments on commit 75c6ad9

Please sign in to comment.