Skip to content

Commit 2ab73cb

Browse files
committed
New Feature: better support for custom messages, closes #148
- add optional message field to ValidationError - add message argument to failure - PathReporter should account for the new field
1 parent 7f95606 commit 2ab73cb

File tree

7 files changed

+47
-24
lines changed

7 files changed

+47
-24
lines changed

README.md

+22
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,28 @@ const Person = t.type({
149149
console.log(getPaths(Person.decode({}))) // => [ '.name', '.age' ]
150150
```
151151

152+
# Custom error messages
153+
154+
You can set your own error message by providing a `message` argument to `failure`
155+
156+
Example
157+
158+
```ts
159+
const NumberFromString = new t.Type<number, string, unknown>(
160+
'NumberFromString',
161+
t.number.is,
162+
(u, c) =>
163+
t.string.validate(u, c).chain(s => {
164+
const n = +s
165+
return isNaN(n) ? t.failure(u, c, 'cannot parse to a number') : t.success(n)
166+
}),
167+
String
168+
)
169+
170+
console.log(PathReporter.report(NumberFromString.decode('a')))
171+
// => ['cannot parse to a number']
172+
```
173+
152174
# Community
153175

154176
- [io-ts-types](https://github.com/gcanti/io-ts-types) - A collection of codecs and combinators for use with

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "io-ts",
3-
"version": "1.6.4",
3+
"version": "1.7.0",
44
"description": "TypeScript compatible runtime type system for IO validation",
55
"files": [
66
"lib"

src/PathReporter.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ function getContextPath(context: Context): string {
99
return context.map(({ key, type }) => `${key}: ${type.name}`).join('/')
1010
}
1111

12-
function getMessage(v: any, context: Context): string {
13-
return `Invalid value ${stringify(v)} supplied to ${getContextPath(context)}`
12+
function getMessage(e: ValidationError): string {
13+
return e.message !== undefined
14+
? e.message
15+
: `Invalid value ${stringify(e.value)} supplied to ${getContextPath(e.context)}`
1416
}
1517

1618
/**
1719
* @since 1.0.0
1820
*/
1921
export function failure(es: Array<ValidationError>): Array<string> {
20-
return es.map(e => getMessage(e.value, e.context))
22+
return es.map(getMessage)
2123
}
2224

2325
/**

src/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface Context extends ReadonlyArray<ContextEntry> {}
2525
export interface ValidationError {
2626
readonly value: unknown
2727
readonly context: Context
28+
readonly message?: string
2829
}
2930

3031
/**
@@ -194,8 +195,8 @@ export const failures = <T>(errors: Errors): Validation<T> => new Left(errors)
194195
/**
195196
* @since 1.0.0
196197
*/
197-
export const failure = <T>(value: unknown, context: Context): Validation<T> =>
198-
failures([getValidationError(value, context)])
198+
export const failure = <T>(value: unknown, context: Context, message?: string): Validation<T> =>
199+
failures([{ value, context, message }])
199200

200201
/**
201202
* @since 1.0.0

test/PathReporter.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as assert from 'assert'
22
import * as t from '../src'
33
import { PathReporter } from '../src/PathReporter'
4+
import { NumberFromString } from './helpers'
45

56
describe('PathReporter', () => {
67
it('should use the function name as error message', () => {
@@ -13,4 +14,8 @@ describe('PathReporter', () => {
1314
it('should say something whene there are no errors', () => {
1415
assert.deepEqual(PathReporter.report(t.number.decode(1)), ['No errors!'])
1516
})
17+
18+
it('should account for the optional message field', () => {
19+
assert.deepEqual(PathReporter.report(NumberFromString.decode('a')), ['cannot parse to a number'])
20+
})
1621
})

test/helpers.ts

+10-17
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,16 @@ export function assertFailure<T>(result: t.Validation<T>, errors: Array<string>)
3939
}
4040
}
4141

42-
class NumberFromStringType extends t.Type<number, string, unknown> {
43-
readonly _tag: 'NumberFromStringType' = 'NumberFromStringType'
44-
constructor() {
45-
super(
46-
'NumberFromString',
47-
t.number.is,
48-
(u, c) =>
49-
t.string.validate(u, c).chain(s => {
50-
const n = +s
51-
return isNaN(n) ? t.failure(u, c) : t.success(n)
52-
}),
53-
String
54-
)
55-
}
56-
}
57-
58-
export const NumberFromString = new NumberFromStringType()
42+
export const NumberFromString = new t.Type<number, string, unknown>(
43+
'NumberFromString',
44+
t.number.is,
45+
(u, c) =>
46+
t.string.validate(u, c).chain(s => {
47+
const n = +s
48+
return isNaN(n) ? t.failure(u, c, 'cannot parse to a number') : t.success(n)
49+
}),
50+
String
51+
)
5952

6053
export const HyphenatedString = new t.Type<string, string, unknown>(
6154
'HyphenatedString',

test/refinement.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ describe('refinement', () => {
5252

5353
it('should fail with the last deserialized value', () => {
5454
const T = IntegerFromString
55-
assertFailure(T.decode('a'), ['Invalid value "a" supplied to : IntegerFromString'])
55+
assertFailure(T.decode('a'), ['cannot parse to a number'])
5656
assertFailure(T.decode('1.2'), ['Invalid value 1.2 supplied to : IntegerFromString'])
5757
})
5858
})

0 commit comments

Comments
 (0)