Open aruediger opened 6 years ago
FYI: this seems to work for now:
import * as t from 'io-ts'
import { either } from 'ramda'
export class ReadonlyArrayWrapperType<RT extends t.Any, A = any, O = A, I = t.mixed> extends t.Type<
A,
O,
I
> {
readonly _tag: 'ReadonlyArrayType' = 'ReadonlyArrayType'
constructor(
name: string,
is: ReadonlyArrayWrapperType<RT, A, O, I>['is'],
validate: ReadonlyArrayWrapperType<RT, A, O, I>['validate'],
serialize: ReadonlyArrayWrapperType<RT, A, O, I>['encode'],
readonly type: RT,
) {
super(name, is, validate, serialize)
}
}
export const readonlyArrayWrapper = <RT extends t.Mixed>(
type: RT,
name: string = `ReadonlyArray<${type.name}>`,
): ReadonlyArrayWrapperType<
RT,
ReadonlyArray<t.TypeOf<RT>>,
ReadonlyArray<t.OutputOf<RT>>,
t.mixed
> => {
const arrayType = t.array(type)
return new ReadonlyArrayWrapperType(
name,
either(arrayType.is, type.is),
(m, c) =>
(Array.isArray(m) ? arrayType.validate(m, c) : type.validate(m, c).map(x => [x])).map(x => {
if (process.env.NODE_ENV !== 'production') {
return Object.freeze(x)
} else {
return x
}
}),
arrayType.encode as any,
type,
)
}
(Array.isArray(m) ? arrayType.validate(m, c) : type.validate(m, c).map(x => [x]))
@2beaucoup this is problematic if type
is in turn an array. I'd do something like
export class ReadonlyArrayWrapperType<RT extends t.Any, A = any, O = A, I = t.mixed> extends t.Type<A, O, I> {
readonly _tag: 'ReadonlyArrayWrapperType' = 'ReadonlyArrayWrapperType'
constructor(
name: string,
is: ReadonlyArrayWrapperType<RT, A, O, I>['is'],
validate: ReadonlyArrayWrapperType<RT, A, O, I>['validate'],
encode: ReadonlyArrayWrapperType<RT, A, O, I>['encode'],
readonly type: RT
) {
super(name, is, validate, encode)
}
}
export const readonlyArrayWrapper = <RT extends t.Mixed>(
type: RT,
name: string = `ReadonlyArrayWrapperType<${type.name}>`
): ReadonlyArrayWrapperType<RT, ReadonlyArray<t.TypeOf<RT>>, ReadonlyArray<t.OutputOf<RT>>, t.mixed> => {
const arrayType = t.readonlyArray(type)
return new ReadonlyArrayWrapperType(
name,
arrayType.is,
(m, c) =>
arrayType.validate(m, t.appendContext(c, '0', arrayType)).orElse(arrayTypeErrors =>
type.validate(m, t.appendContext(c, '1', type)).bimap(
typeErrors => arrayTypeErrors.concat(typeErrors),
x => {
if (process.env.NODE_ENV !== 'production') {
return Object.freeze([x])
} else {
return [x]
}
}
)
),
arrayType.encode,
type
)
}
Usage
import { PathReporter } from 'io-ts/lib/PathReporter'
const T = readonlyArrayWrapper(t.string)
console.log(PathReporter.report(T.decode('a')))
console.log(PathReporter.report(T.decode(undefined)))
console.log(PathReporter.report(T.decode([1])))
console.log(PathReporter.report(T.decode(1)))
/*
Output:
[ 'No errors!' ]
[ 'Invalid value undefined supplied to : ReadonlyArrayWrapperType<string>/0: ReadonlyArray<string>',
'Invalid value undefined supplied to : ReadonlyArrayWrapperType<string>/1: string' ]
[ 'Invalid value 1 supplied to : ReadonlyArrayWrapperType<string>/0: ReadonlyArray<string>/0: string',
'Invalid value [1] supplied to : ReadonlyArrayWrapperType<string>/1: string' ]
[ 'Invalid value 1 supplied to : ReadonlyArrayWrapperType<string>/0: ReadonlyArray<string>',
'Invalid value 1 supplied to : ReadonlyArrayWrapperType<string>/1: string' ]
*/
Awesome, thanks a lot @gcanti!
So no higher order function that does the trick I guess.
do you mean mapInput
? I'm not sure how would be its signature
i'm not sure. i guess it would expect a function that receives the original validator and returns a new, adjusted one.
I'd like to use something similar to mapOutput but want to transform the input during validation. Use case would be
so that my input could either be an array of type T or an object of type T but after validation I'd always get an array. I know that this makes serialisation/deserialisation non-roundtrippable but that would be ok.
Are there maybe any higher order function I did miss? Or do I have to create a completely new type?