Skip to content

Commit

Permalink
feat: Adds sanitizeJsonAsString (#410)
Browse files Browse the repository at this point in the history
* feat: Adds sanitizeJsonAsString

* fix: Removes unused import
  • Loading branch information
ryasmi authored Jul 9, 2021
1 parent b38a476 commit aabaf21
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 7 deletions.
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ In addition to the constrained strings, Rulr also comes with a few convenient ru
Finally, Rulr is starting to provide rules that sanitize inputs.

- [sanitizeBooleanAsString](./src/sanitizationRules/sanitizeBooleanAsString/readme.md)
- [sanitizeJsonAsString](./src/sanitizationRules/sanitizeJsonAsString/readme.md)
- [sanitizeNumberAsString](./src/sanitizationRules/sanitizeNumberAsString/readme.md)

### Frequently Awesome Questions 🤘
Expand Down
18 changes: 12 additions & 6 deletions src/rulr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,6 @@ 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 {
sanitizeNumberAsString,
isNumberAsString,
NumberAsString,
InvalidNumberAsStringError,
} from './sanitizationRules/sanitizeNumberAsString/sanitizeNumberAsString'
export {
sanitizeBooleanAsString,
isBooleanAsString,
Expand All @@ -96,6 +90,18 @@ export {
truthyBooleanStrings,
falsyBooleanStrings,
} from './sanitizationRules/sanitizeBooleanAsString/sanitizeBooleanAsString'
export {
sanitizeJsonAsString,
isJsonAsString,
JsonAsString,
InvalidJsonAsStringError,
} from './sanitizationRules/sanitizeJsonAsString/sanitizeJsonAsString'
export {
sanitizeNumberAsString,
isNumberAsString,
NumberAsString,
InvalidNumberAsStringError,
} from './sanitizationRules/sanitizeNumberAsString/sanitizeNumberAsString'
export { any } from './valueRules/any/any'
export { bigint, isBigInt, InvalidBigIntError } from './valueRules/bigint/bigint'
export { boolean, isBoolean, InvalidBooleanError } from './valueRules/boolean/boolean'
Expand Down
48 changes: 48 additions & 0 deletions src/sanitizationRules/sanitizeJsonAsString/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# sanitizeJsonAsString

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

This function uses `rulr.isJsonAsString` and can be used when you want to sanitize an input to be a string containing JSON as shown in the example below. This function is a higher order rule as it uses a sub-rule to validate input that is JSON. This function should only throw `rulr.InvalidJsonAsStringError`, `SyntaxError`, and errors from the sub-rule.

```ts
import * as rulr from 'rulr'

const constrainToExample = rulr.object({
required: {
example: rulr.sanitizeJsonAsString(
rulr.object({
required: {
someProp: rulr.positiveInteger,
},
})
),
},
})

type Example = rulr.Static<typeof constrainToExample>
// {
// example: {
// someProp: rulr.PositiveInteger
// }
// }

// Valid
const example1: Example = constrainToExample({
example: '{ "someProp": 1 }',
})

// Invalid: Not a positive integer - InvalidPositiveIntegerError
const example2: Example = constrainToExample({
example: '{ "someProp": -1 }',
})

// Invalid: Not JSON - SyntaxError
const example3: Example = constrainToExample({
example: 'abc',
})

// Invalid: Not a string - InvalidJsonAsStringError
const example4: Example = constrainToExample({
example: 1,
})
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as assert from 'assert'
import * as rulr from '../../rulr'

test('sanitizeJsonAsString should allow JSON as a string', () => {
interface Output {
someProp: number
}
const input = '{ "someProp": 1 }'
const rule = rulr.sanitizeJsonAsString(
rulr.object({
required: {
someProp: rulr.number,
},
})
)
const output: Output = rule(input)
assert.deepStrictEqual(output, { someProp: 1 })
})

test('sanitizeJsonAsString should now allow value that does not match the sub rule', () => {
const input = 'true'
const rule = rulr.sanitizeJsonAsString(rulr.number)
assert.throws(() => rule(input), rulr.InvalidNumberError)
})

test('sanitizeJsonAsString should now allow value that is not JSON as a string', () => {
const input = 'abc'
const rule = rulr.sanitizeJsonAsString(rulr.number)
assert.throws(() => rule(input), SyntaxError)
})

test('sanitizeJsonAsString should now allow value that is not a string', () => {
const input = 1
const rule = rulr.sanitizeJsonAsString(rulr.number)
assert.throws(() => rule(input), rulr.InvalidJsonAsStringError)
})

test('isJsonAsString should return true for valid JSON', () => {
assert.strictEqual(rulr.isJsonAsString('{ "someProp": 1 }'), true)
})

test('isJsonAsString should return false when the value is not a string', () => {
assert.strictEqual(rulr.isJsonAsString(1), false)
})

test('isJsonAsString should return false when the value is not JSON', () => {
assert.strictEqual(rulr.isJsonAsString('abc'), false)
})
35 changes: 35 additions & 0 deletions src/sanitizationRules/sanitizeJsonAsString/sanitizeJsonAsString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { BaseError } from 'make-error'
import { isString } from '../../valueRules/string/string'
import { Constrained, Rule } from '../../core'

export class InvalidJsonAsStringError extends BaseError {
constructor() {
super('expected JSON as string')
}
}

export const jsonAsStringSymbol = Symbol()

export type JsonAsString = Constrained<typeof jsonAsStringSymbol, string>

function isJson(input: string) {
try {
JSON.parse(input)
return true
} catch {
return false
}
}

export function isJsonAsString(input: unknown): input is JsonAsString {
return isString(input) && isJson(input)
}

export function sanitizeJsonAsString<T>(jsonRule: Rule<T>) {
return (input: unknown) => {
if (isString(input)) {
return jsonRule(JSON.parse(input))
}
throw new InvalidJsonAsStringError()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test('sanitizeNumberAsString should now allow value that is not a number as a st
assert.throws(() => rule(input), InvalidNumberAsStringError)
})

test('sanitizeNumberAsString should now allow value that does not match rule', () => {
test('sanitizeNumberAsString should now allow value that does not match the sub rule', () => {
const input = '-1'
const rule = sanitizeNumberAsString(positiveInteger)
assert.throws(() => rule(input), InvalidPositiveIntegerError)
Expand Down

0 comments on commit aabaf21

Please sign in to comment.