@typed/fp
Version:
Data Structures and Resources for fp-ts
324 lines (295 loc) • 7.03 kB
text/typescript
/**
* DecodeError representation of the various errors that might occur while decoding.
* @since 0.9.4
*/
import { flow, pipe } from 'fp-ts/function'
import * as RA from 'fp-ts/ReadonlyArray'
import { ReadonlyNonEmptyArray } from 'fp-ts/ReadonlyNonEmptyArray'
import { Semigroup } from 'fp-ts/Semigroup'
import * as T from 'fp-ts/Tree'
/**
* @category Model
* @since 0.9.4
*/
export interface Leaf {
readonly _tag: 'Leaf'
readonly actual: unknown
readonly error: string
}
/**
* @category Model
* @since 0.9.4
*/
export interface Key {
readonly _tag: 'Key'
readonly key: string
readonly errors: DecodeErrors
}
/**
* @category Model
* @since 0.9.4
*/
export interface MissingKeys {
readonly _tag: 'MissingKeys'
readonly keys: readonly [string, ...string[]]
}
/**
* @category Model
* @since 0.9.4
*/
export interface UnexpectedKeys {
readonly _tag: 'UnexpectedKeys'
readonly keys: readonly [string, ...string[]]
}
/**
* @category Model
* @since 0.9.4
*/
export interface Index {
readonly _tag: 'Index'
readonly index: number
readonly errors: DecodeErrors
}
/**
* @category Model
* @since 0.9.4
*/
export interface MissingIndexes {
readonly _tag: 'MissingIndexes'
readonly indexes: readonly [number, ...number[]]
}
/**
* @category Model
* @since 0.9.4
*/
export interface UnexpectedIndexes {
readonly _tag: 'UnexpectedIndexes'
readonly indexes: readonly [number, ...number[]]
}
/**
* @category Model
* @since 0.9.4
*/
export interface Member {
readonly _tag: 'Member'
readonly index: number
readonly errors: DecodeErrors
}
/**
* @category Model
* @since 0.9.4
*/
export interface Lazy {
readonly _tag: 'Lazy'
readonly id: string
readonly errors: DecodeErrors
}
/**
* @category Model
* @since 2.2.9
*/
export interface Wrap {
readonly _tag: 'Wrap'
readonly error: string
readonly errors: DecodeErrors
}
/**
* @category Model
* @since 0.9.4
*/
export type DecodeError =
| Leaf
| Key
| MissingKeys
| UnexpectedKeys
| Index
| MissingIndexes
| UnexpectedIndexes
| Member
| Lazy
| Wrap
/**
* @since 0.9.4
* @categeory Model
*/
export type DecodeErrors = ReadonlyNonEmptyArray<DecodeError>
/**
* @category constructors
* @since 0.9.4
*/
export const leaf = (actual: unknown, error: string): DecodeError => ({
_tag: 'Leaf',
actual,
error,
})
/**
* @category constructors
* @since 0.9.4
*/
export const key = (key: string, errors: DecodeErrors): DecodeError => ({
_tag: 'Key',
key,
errors,
})
/**
* @category constructors
* @since 0.9.4
*/
export const missingKeys = (keys: readonly [string, ...string[]]): DecodeError => ({
_tag: 'MissingKeys',
keys,
})
/**
* @category constructors
* @since 0.9.4
*/
export const unexpectedKeys = (keys: readonly [string, ...string[]]): DecodeError => ({
_tag: 'UnexpectedKeys',
keys,
})
/**
* @category constructors
* @since 0.9.4
*/
export const index = (index: number, errors: DecodeErrors): DecodeError => ({
_tag: 'Index',
index,
errors,
})
/**
* @category constructors
* @since 0.9.4
*/
export const missingIndexes = (indexes: readonly [number, ...number[]]): DecodeError => ({
_tag: 'MissingIndexes',
indexes,
})
/**
* @category constructors
* @since 0.9.4
*/
export const unexpectedIndexes = (indexes: readonly [number, ...number[]]): DecodeError => ({
_tag: 'UnexpectedIndexes',
indexes,
})
/**
* @category constructors
* @since 0.9.4
*/
export const member = (index: number, errors: DecodeErrors): DecodeError => ({
_tag: 'Member',
index,
errors,
})
/**
* @category constructors
* @since 0.9.4
*/
export const lazy = (id: string, errors: DecodeErrors): DecodeError => ({
_tag: 'Lazy',
id,
errors,
})
/**
* @category constructors
* @since 2.2.9
*/
export const wrap = (error: string, errors: DecodeErrors): DecodeError => ({
_tag: 'Wrap',
error,
errors,
})
/**
* @category destructors
* @since 0.9.4
*/
export const match = <R>(patterns: {
Leaf: (input: unknown, error: string) => R
Key: (key: string, errors: DecodeErrors) => R
MissingKeys: (keys: readonly [string, ...string[]]) => R
UnexpectedKeys: (keys: readonly [string, ...string[]]) => R
Index: (index: number, errors: DecodeErrors) => R
MissingIndexes: (indexes: readonly [number, ...number[]]) => R
UnexpectedIndexes: (keys: readonly [number, ...number[]]) => R
Member: (index: number, errors: DecodeErrors) => R
Lazy: (id: string, errors: DecodeErrors) => R
Wrap: (error: string, errors: DecodeErrors) => R
}): ((e: DecodeError) => R) => {
const f = (e: DecodeError): R => {
switch (e._tag) {
case 'Leaf':
return patterns.Leaf(e.actual, e.error)
case 'Key':
return patterns.Key(e.key, e.errors)
case 'MissingKeys':
return patterns.MissingKeys(e.keys)
case 'UnexpectedKeys':
return patterns.UnexpectedKeys(e.keys)
case 'Index':
return patterns.Index(e.index, e.errors)
case 'MissingIndexes':
return patterns.MissingIndexes(e.indexes)
case 'UnexpectedIndexes':
return patterns.UnexpectedIndexes(e.indexes)
case 'Member':
return patterns.Member(e.index, e.errors)
case 'Lazy':
return patterns.Lazy(e.id, e.errors)
case 'Wrap':
return patterns.Wrap(e.error, e.errors)
}
}
return f
}
/**
* @category Typeclass Constructor
* @since 0.9.4
*/
export function getSemigroup(): Semigroup<DecodeErrors> {
return RA.getSemigroup<DecodeError>() as any as Semigroup<DecodeErrors>
}
/**
* @category Deconstructor
* @since 0.9.4
*/
export const drawError = flow(toTree, T.drawTree)
/**
* @category Deconstructor
* @since 0.9.4
*/
export const drawErrors = flow(RA.map(flow(toTree, T.drawTree)), (ss) => ss.join('\n'))
function toForest(errors: DecodeErrors): T.Forest<string> {
return errors.map(toTree)
}
function toTree(error: DecodeError): T.Tree<string> {
return pipe(
error,
match({
Leaf: (i, error) => T.of(`Expected ${error} but received ${JSON.stringify(i)}`),
Key: (key, errors) => ({ value: `Key ${key}`, forest: toForest(errors) }),
MissingKeys: (keys) => ({ value: `MissingKeys`, forest: pipe(keys, RA.map(T.of)) }),
UnexpectedKeys: (keys) => ({ value: `UnexpectedKeys`, forest: pipe(keys, RA.map(T.of)) }),
Index: (Index, errors) => ({ value: `Index ${Index}`, forest: toForest(errors) }),
MissingIndexes: (indexes) => ({
value: `Missing indexes`,
forest: pipe(indexes, RA.map(flow(String, T.of))),
}),
UnexpectedIndexes: (indexes) => ({
value: `Unexpected Indexes`,
forest: pipe(indexes, RA.map(flow(String, T.of))),
}),
Member: (index, errors) => ({
value: `${index}`,
forest: toForest(errors),
}),
Lazy: (id, errors) => ({
value: id,
forest: toForest(errors),
}),
Wrap: (error, errors) => ({
value: error,
forest: toForest(errors),
}),
}),
)
}