-
Notifications
You must be signed in to change notification settings - Fork 328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support request: strict intersections of interface and partial #106
Comments
@gunzip are you extracting the static type from If you are not extracting the static type you should be able to avoid using const T = t.strict({
foo: t.string,
bar: t.union([t.string, t.undefined])
}) However currently this is failing console.log(
t.validate({ foo: 'foo' }, T).fold(errors => {
throw new Error(failure(errors).join('\n'))
}, t.identity)
)
// Invalid value {"foo":"foo"} supplied to : StrictType<{ foo: string, bar: (string | undefined) }> which means that const T = t.interface({
foo: t.string,
bar: t.union([t.string, t.undefined])
})
console.log(
t.validate({ foo: 'foo' }, T).fold(errors => {
throw new Error(failure(errors).join('\n'))
}, t.identity)
)
// { foo: 'foo' } |
Yes indeed I'm extracting the static types and I can live without partials. |
Then it's not possible with the available combinators, you could define your own though, something along the lines of function strictInterfaceWithOptionals<R extends t.Props, O extends t.Props>(
required: R,
optional: O,
name?: string
): t.Type<any, t.InterfaceOf<R> & t.PartialOf<O>> {
const loose = t.intersection([t.interface(required), t.partial(optional)])
const props = Object.assign({}, required, optional)
return new t.Type(
name || `StrictInterfaceWithOptionals(${loose.name})`,
loose.is,
(s, c) =>
loose.validate(s, c).chain(o => {
const keys = Object.getOwnPropertyNames(o)
const len = keys.length
const errors: t.Errors = []
for (let i = 0; i < len; i++) {
const key = keys[i]
if (!props.hasOwnProperty(key)) {
errors.push(t.getValidationError(o[key], t.appendContext(c, key, t.never)))
}
}
return errors.length ? t.failures(errors) : t.success(o)
}),
loose.serialize
)
}
const T = strictInterfaceWithOptionals(
{
foo: t.string
},
{
bar: t.union([t.string, t.undefined])
}
)
console.log(
t.validate({ foo: 'foo', a: 1 }, T).fold(errors => {
throw new Error(failure(errors).join('\n'))
}, t.identity)
)
// Invalid value 1 supplied to : StrictMixedInterface(({ foo: string } & PartialType<{ bar: (string | undefined) }>))/a: never |
thank you this works pretty well ! maybe it's a common case that worths a mention in the docs at least ? export function strictInterfaceWithOptionals<
R extends t.Props,
O extends t.Props
>(
required: R,
optional: O,
name: string
): t.Type<{}, t.InterfaceOf<R> & t.PartialOf<O>> {
const loose = t.intersection([t.interface(required), t.partial(optional)]);
const props = Object.assign({}, required, optional);
return new t.Type(
name,
loose.is,
(s, c) =>
loose.validate(s, c).chain(o => {
const errors: t.Errors = Object.getOwnPropertyNames(o)
.map(
key =>
!props.hasOwnProperty(key)
? t.getValidationError(o[key], t.appendContext(c, key, t.never))
: undefined
)
.filter((e): e is t.ValidationError => e !== undefined);
return errors.length ? t.failures(errors) : t.success(o);
}),
loose.serialize
);
} |
Note: in my implementation (v): v is t.InterfaceOf<R> & t.PartialOf<O> =>
loose.is(v) && Object.getOwnPropertyNames(v).every(k => props.hasOwnProperty(k))
Sure, maybe in the "Recipe" section (https://github.com/gcanti/io-ts#recipes). I'll gladly accept a PR. |
hi @gcanti , may you suggest a way to refactor this with the latest 1.x version ? export function strictInterfaceWithOptionals<
R extends t.Props,
O extends t.Props
>(
required: R,
optional: O,
name: string
): t.Type<t.InterfaceType<R> & t.PartialType<O>, t.mixed> {
const loose = t.intersection([t.interface(required), t.partial(optional)]);
const props = Object.assign({}, required, optional);
return new t.Type(
name,
(v): v is t.InterfaceType<R> & t.PartialType<O> =>
loose.is(v) &&
Object.getOwnPropertyNames(v).every(k => props.hasOwnProperty(k)),
// tslint:disable-next-line:readonly-array
(s: t.mixed, c: t.ContextEntry[]) =>
loose.validate(s, c).chain(o => {
const errors: t.Errors = Object.getOwnPropertyNames(o)
.map(
key =>
!props.hasOwnProperty(key)
? t.getValidationError(o[key], t.appendContext(c, key, t.never))
: undefined
)
.filter((e): e is t.ValidationError => e !== undefined);
return errors.length ? t.failures(errors) : t.success(o);
}),
loose.encode
);
} |
@gunzip try this export function strictInterfaceWithOptionals<R extends t.Props, O extends t.Props>(
required: R,
optional: O,
name?: string
): t.Type<
{ [K in keyof R]: t.TypeOf<R[K]> } & { [K in keyof O]?: t.TypeOf<O[K]> },
{ [K in keyof R]: t.OutputOf<R[K]> } & { [K in keyof O]?: t.OutputOf<O[K]> }
> {
const loose = t.intersection([t.interface(required), t.partial(optional)])
const props = Object.assign({}, required, optional)
return new t.Type(
name || `StrictInterfaceWithOptionals(${loose.name})`,
(m): m is t.TypeOfProps<R> & t.TypeOfPartialProps<O> =>
loose.is(m) && Object.getOwnPropertyNames(m).every(k => props.hasOwnProperty(k)),
(m, c) =>
loose.validate(m, c).chain(o => {
const errors: t.Errors = Object.getOwnPropertyNames(o)
.map(
key =>
!props.hasOwnProperty(key) ? t.getValidationError(o[key], t.appendContext(c, key, t.never)) : undefined
)
.filter((e): e is t.ValidationError => e !== undefined)
return errors.length ? t.failures(errors) : t.success(o)
}),
loose.encode
)
} |
It looks like it's working thank you ! (I'm in the middle of the big refactor to 1.x =) By now I've:
|
You can use import { fromEither } from 'fp-ts/lib/Option' |
Is it possible to have a "strict" intersection of optional and mandatory properties ?
I've tried the following without success:
or:
The text was updated successfully, but these errors were encountered: