io-ts-extra
Version:
Adds pattern matching, optional properties, and several other helpers and types, to io-ts.
124 lines • 6.67 kB
TypeScript
import * as Either from 'fp-ts/lib/Either';
import * as t from 'io-ts';
import { IsNeverOrAny } from './util';
import { ShorthandInput, Shorthand } from './shorthand';
/** Not a real type that anything will have at runtime. Just a way of giving helpful compiler errors. */
declare type UnionOfCasesDoesNotMatchExpected<InSoFar, In> = {
/** compile time only. basically a fake property to signal at compile time that you didn't exhaustively match before calling `.get`. Don't try to use this value, it won't exist! */
_message: 'type union of inputs for cases does not match expected input type. try adding more case statements or use .default(...)';
/** compile time only. basically a fake property to signal at compile time that you didn't exhaustively match before calling `.get`. Don't try to use this value, it won't exist! */
_actual: InSoFar;
/** compile time only. basically a fake property to signal at compile time that you didn't exhaustively match before calling `.get`. Don't try to use this value, it won't exist! */
_expected: In;
/** compile time only. basically a fake property to signal at compile time that you didn't exhaustively match before calling `.get`. Don't try to use this value, it won't exist! */
_unhandled: Exclude<In, InSoFar>;
} | never;
declare type Mappable<In, NextIn> = IsNeverOrAny<In> extends 1 ? NextIn : In & NextIn;
interface MatcherBuilder<In, InSoFar, Out> {
case: {
<NextIn, MapperIn extends Mappable<In, NextIn>, NextOut>(type: t.RefinementType<t.Type<NextIn>>, map: (obj: MapperIn) => NextOut): MatcherBuilder<In, InSoFar, Out | NextOut>;
<NextIn extends ShorthandInput, NextOut>(shorthand: NextIn, map: (obj: Mappable<In, Shorthand<NextIn>['_A']>) => NextOut): MatcherBuilder<In, InSoFar | Shorthand<NextIn>['_A'], Out | NextOut>;
<NextIn extends ShorthandInput, NextOut>(shorthand: NextIn, predicate: (value: Shorthand<NextIn>['_A']) => boolean, map: (obj: Mappable<In, Shorthand<NextIn>['_A']>) => NextOut): MatcherBuilder<In, InSoFar | Shorthand<NextIn>['_A'], Out | NextOut>;
};
default: <NextOut>(map: (obj: In) => NextOut) => MatcherBuilder<In, any, Out | NextOut>;
get: IsNeverOrAny<Exclude<In, InSoFar>> extends 1 ? (obj: InSoFar) => Out : UnionOfCasesDoesNotMatchExpected<InSoFar, In>;
tryGet: (obj: In) => Hopefully<Out>;
}
interface PatternMatchBuilder<In, InSoFar, Out> {
case: {
<NextIn, NextOut>(type: t.RefinementType<t.Type<NextIn>>, map: (obj: Mappable<In, NextIn>) => NextOut): PatternMatchBuilder<In, InSoFar, Out | NextOut>;
<NextIn extends ShorthandInput, NextOut>(shorthand: NextIn, map: (obj: Mappable<In, Shorthand<NextIn>['_A']>) => NextOut): PatternMatchBuilder<In, InSoFar | Shorthand<NextIn>['_A'], Out | NextOut>;
<NextIn extends ShorthandInput, NextOut>(shorthand: NextIn, predicate: (value: Shorthand<NextIn>['_A']) => boolean, map: (obj: Mappable<In, Shorthand<NextIn>['_A']>) => NextOut): PatternMatchBuilder<In, InSoFar | Shorthand<NextIn>['_A'], Out | NextOut>;
};
default: <NextOut>(map: (obj: In) => NextOut) => PatternMatchBuilder<In, any, Out | NextOut>;
get: IsNeverOrAny<Exclude<In, InSoFar>> extends 1 ? () => Out : UnionOfCasesDoesNotMatchExpected<InSoFar, In>;
}
/**
* Match an object against a number of cases. Loosely based on Scala's pattern matching.
*
* @example
* // get a value which could be a string or a number:
* const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10
* const stringified = match(value)
* .case(String, s => `the message is ${s}`)
* .case(7, () => 'exactly seven')
* .case(Number, n => `the number is ${n}`)
* .get()
*
* @description
* Under the hood, io-ts is used for validation. The first argument can be a "shorthand" for a type,
* but you can also pass in io-ts codecs directly for more complex types:
*
* @example
* // get a value which could be a string or a number:
* const value = Math.random() < 0.5 ? 'foo' : 123
* const stringified = match(value)
* .case(t.number, n => `the number is ${n}`)
* .case(t.string, s => `the message is ${s}`)
* .get()
*
* @description
* you can use a predicate function or `t.refinement` for the equivalent of scala's `case x: Int if x > 2`:
*
* @example
* // value which could be a string, or a real number in [0, 10):
* const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10
* const stringified = match(value)
* .case(Number, n => n > 2, n => `big number: ${n}`)
* .case(Number, n => `small number: ${n}`)
* .default(x => `not a number: ${x}`)
* .get()
*
* @example
* // value which could be a string, or a real number in [0, 10):
* const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10
* const stringified = match(value)
* .case(t.refinement(t.number, n => n > 2), n => `big number: ${n}`)
* .case(t.number, n => `small number: ${n}`)
* .default(x => `not a number: ${x}`)
* .get()
*
* @description
*
* note: when using predicates or `t.refinement`, the type being refined is not considered exhaustively matched,
* so you'll usually need to add a non-refined option, or you can also use `.default` as a fallback
* case (the equivalent of `.case(t.any, ...)`)
*
* @param obj the object to be pattern-matched
*/
export declare const match: <Input>(obj: Input) => PatternMatchBuilder<Input, never, never>;
/**
* Like @see match but no object is passed in when constructing the case statements.
* Instead `.get` is a function into which a value should be passed.
*
* @example
* const Email = t.type({sender: t.string, subject: t.string, body: t.string})
* const SMS = t.type({from: t.string, content: t.string})
* const Message = t.union([Email, SMS])
* type Message = typeof Message._A
*
* const content = matcher<MessageType>()
* .case(SMS, s => s.content)
* .case(Email, e => e.subject + '\n\n' + e.body)
* .get({from: '123', content: 'hello'})
*
* expect(content).toEqual('hello')
*
* @description
* The function returned by `.get` is stateless and has no `this` context,
* you can store it in a variable and pass it around:
*
* @example
* const getContent = matcher<Message>()
* .case(SMS, s => s.content)
* .case(Email, e => e.subject + '\n\n' + e.body)
* .get
*
* const allMessages: Message[] = getAllMessages();
* const contents = allMessages.map(getContent);
*/
export declare const matcher: <In = any>() => MatcherBuilder<In, never, never>;
export declare const collect: <T, U>(items: T[], partialFunc: (t: T) => Either.Either<unknown, U>) => U[];
export declare type Hopefully<T> = Either.Either<unknown, T>;
export {};
//# sourceMappingURL=match.d.ts.map