-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(errors): Add information about nested fields with errors (#61)
* feat(errors): Add information about nested fields with errors * v0.7.4-0 * wip * Working implementation * Coverage * Final touch
- Loading branch information
Showing
25 changed files
with
302 additions
and
154 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { number, object, string, validate, date, pipe, ValidationError, refine } from '../src'; | ||
import { modifierToString } from '../src/refine'; | ||
|
||
const expectToMatchError = (fn: () => any, obj: Record<string, any>) => { | ||
try { | ||
fn(); | ||
} catch (err) { | ||
if (err instanceof ValidationError) { | ||
return expect(err.getDetails()).toStrictEqual(obj); | ||
} | ||
fail(err); | ||
} | ||
fail(); | ||
}; | ||
|
||
describe('errors', () => { | ||
it('should list fields in object which are incorrect', () => { | ||
const validator = pipe( | ||
object({ | ||
validString: string(), | ||
invalidNumber: number(), | ||
validNumber: number(), | ||
invalidString: string(), | ||
validDate: date(), | ||
invalidDate: date(), | ||
}), | ||
validate, | ||
); | ||
|
||
expectToMatchError( | ||
() => | ||
validator({ | ||
validString: 'aaa', | ||
invalidNumber: 'vvvv', | ||
validNumber: 123, | ||
invalidString: 1333, | ||
validDate: new Date('2020'), | ||
invalidDate: 'no siema eniu', | ||
}), | ||
{ | ||
invalidNumber: { expected: 'number', got: 'vvvv' }, | ||
invalidString: { expected: 'string', got: 1333 }, | ||
invalidDate: { expected: 'Date', got: 'no siema eniu' }, | ||
}, | ||
); | ||
}); | ||
|
||
it('should list nested objects which are undefined', () => { | ||
const validator = pipe( | ||
object({ | ||
nested: object({ | ||
invalid: string(), | ||
})(), | ||
}), | ||
validate, | ||
); | ||
|
||
expectToMatchError(() => validator({}), { | ||
nested: { expected: '{ invalid: string }', got: undefined }, | ||
}); | ||
}); | ||
|
||
it('should list nested objects which are undefined', () => { | ||
const validator = pipe( | ||
object({ | ||
a: number(), | ||
nested: object({ | ||
invalid: string(), | ||
})(), | ||
}), | ||
validate, | ||
); | ||
|
||
expectToMatchError(() => validator({ a: 123 }), { | ||
nested: { expected: '{ invalid: string }', got: undefined }, | ||
}); | ||
}); | ||
|
||
it('should use custom refinement to string', () => { | ||
const email = refine<string, string>( | ||
(value: string, t) => (value.includes('@') ? t.nextValid(value) : t.left(value)), | ||
modifierToString('email'), | ||
); | ||
|
||
const validator = pipe( | ||
object({ | ||
// shouldBeEmail: string(email()), | ||
shouldBeEmail: string(email()), | ||
}), | ||
validate, | ||
); | ||
|
||
expectToMatchError(() => validator({ shouldBeEmail: 123 }), { | ||
shouldBeEmail: { expected: 'email(string)', got: 123 }, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,53 @@ | ||
import { schemaToString } from './stringify'; | ||
import type { SomeSchema } from './types'; | ||
import type { Primitives, SomeSchema } from './types'; | ||
|
||
export type Result = Record<string, ValidationError> | Primitives | Date | ValidationError; | ||
|
||
export class ValidationError extends Error { | ||
public readonly details: ErrorDetails; | ||
private readonly schema: SomeSchema<any> | undefined; | ||
private readonly value: unknown; | ||
private readonly result?: Result; | ||
|
||
constructor(schema: SomeSchema<any>, value: any) { | ||
const expected = schemaToString(schema); | ||
const got = typeof value === 'function' ? String(value) : JSON.stringify(value); | ||
constructor(schema?: SomeSchema<any> | undefined, value?: unknown, result?: Result) { | ||
const expectedStr = schemaToString(schema); | ||
const gotStr = typeof value === 'function' ? String(value) : JSON.stringify(value); | ||
|
||
const details: ErrorDetails = { | ||
kind: 'TYPE_MISMATCH', | ||
got, | ||
expected, | ||
fields: result, | ||
}; | ||
super(`Invalid type! Expected ${details.expected} but got ${details.got}!`); | ||
super(`Invalid type! Expected ${expectedStr} but got ${gotStr}!`); | ||
|
||
this.details = details; | ||
this.name = 'ValidationError'; | ||
this.schema = schema; | ||
this.value = value; | ||
this.result = result; | ||
Error.captureStackTrace(this); | ||
|
||
Object.setPrototypeOf(this, ValidationError.prototype); | ||
} | ||
|
||
getDetails(): Record<string, any> { | ||
if (this.result instanceof ValidationError) { | ||
return this.result.getDetails(); | ||
} else if (typeof this.result === 'object' && this.result && !Array.isArray(this.result)) { | ||
return Object.fromEntries( | ||
Object.entries(this.result).map(([key, error]) => { | ||
/* istanbul ignore else */ | ||
if (error instanceof ValidationError) { | ||
return [key, error.getDetails()]; | ||
} else { | ||
return [key, error]; | ||
} | ||
}), | ||
); | ||
} else { | ||
return { expected: schemaToString(this.schema), got: this.value }; | ||
} | ||
} | ||
} | ||
|
||
type ErrorDetails = { | ||
readonly kind: 'TYPE_MISMATCH'; | ||
readonly expected: string; | ||
readonly got: string; | ||
readonly fields: any; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
import { refine } from '../refine'; | ||
import { modifierToString, refine } from '../refine'; | ||
|
||
export const minStringLength = <L extends number>(minLength: L) => | ||
refine((value: string, t) => (value.length >= minLength ? t.nextValid(value) : t.left(value))); | ||
refine( | ||
(value: string, t) => (value.length >= minLength ? t.nextValid(value) : t.left(value)), | ||
modifierToString(`minStringLength(${minLength})`), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,6 @@ | ||
import { refine } from '../refine'; | ||
import { typeToPrint } from '../stringify'; | ||
|
||
export const nullable = refine( | ||
(value, t) => (value === null ? t.right(null) : t.nextNotValid(value)), | ||
() => typeToPrint('null'), | ||
`null`, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,6 @@ | ||
import { refine } from '../refine'; | ||
import { typeToPrint } from '../stringify'; | ||
|
||
export const optional = refine( | ||
(value, t) => (value === undefined ? t.right(undefined) : t.nextNotValid(value)), | ||
() => typeToPrint('undefined'), | ||
`undefined`, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
77312ff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs: