Skip to content

Commit

Permalink
matcher supports custom key property, refactoring.
Browse files Browse the repository at this point in the history
  • Loading branch information
paarthenon committed Dec 26, 2020
1 parent af98d30 commit ec65921
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 185 deletions.
9 changes: 6 additions & 3 deletions docu/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ Summary of the changes in each patch.

## 2.0.3
- Added `matcher()` function
- Added `constrainedVariant()` and `patternedVariant()`.
- Added match helpers `just()` and `unpack()`
- Added `constrained()`, `patterned()`, and `augmented()`.
- Added match helpers `just()` (alias for `constant()`) and `unpack()`
- `outputTypes()` gets a more specific return type.
## 2.0.2
- Added `isType` utility
- and a curried overload
Expand All @@ -18,6 +19,8 @@ Summary of the changes in each patch.
- their functionality is now covered by `match`
- `variantModule` also accepts `{}`
- improved generic handling (`genericVariant`)
- Acknowledgments
- *Thank you [@ohana54](https://github.com/paarthenon/variant/issues/7) for the discussion that led to `isType` and the `match` overloads.*

## 2.0.1
- exposed `variantModule`
Expand All @@ -27,4 +30,4 @@ Summary of the changes in each patch.
- added recursive and generic variants
- added `variantModule`
- `variantList` now accepts raw string literals
- match
- match gets a helper, `constant()`
92 changes: 92 additions & 0 deletions src/deprecated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* THIS FILE is for the content I intent to remove on Variant 3.0 that
* I'm tired of seeing or considering in the code, so it gets shuffled
* off to here.
*/
import {Defined, Handler, match} from './match';
import {Func, Identity} from './util';
import {Outputs, Property, VariantModule, VariantsOfUnion} from './variant';


/**
* @deprecated
* A variant with some extra properties attached (use augmented instead)
*/
export type AugmentVariant<T extends VariantModule, U> = {
[P in keyof T]: ((...args: Parameters<T[P]>) => Identity<ReturnType<T[P]> & U>) & Outputs<T[P]['key'], T[P]['type']>
}

/**
* @deprecated
* Expand the functionality of a variant as a whole by tacking on properties
* generated by a thunk.
* @param variantDef
* @param f
*/
export function augment<T extends VariantModule, F extends Func>(variantDef: T, f: F) {
return Object.keys(variantDef).reduce((acc, key) => {
const augmentedFuncWrapper = (...args: any[]) => (Object.assign({}, f(), variantDef[key](...args)));
return {
...acc,
[key]: Object.assign(augmentedFuncWrapper, {key: variantDef[key].key, type: variantDef[key].type})
};
}, {} as AugmentVariant<T, ReturnType<F>>);
}



/**
* Built to describe an object with the same keys as a variant but instead of constructors
* for those objects has functions that handle objects of that type. In this case, the
* keys are all partial and there is an extra option "default"
*/
export type DefaultedHandler<T, U = any> = Partial<Handler<T> & {default?: (union: T[keyof T]) => U}>

/**
* Match a variant against some of its possible options and do some
* processing based on the type of variant received. May return undefined
* if the variant is not accounted for by the handler.
* @deprecated
* @param obj
* @param handler
* @param typeKey override the property to inspect. By default, 'type'.
*/
export function partialMatch<
T extends Property<K, string>,
K extends string = 'type'
> (
obj: T,
handler: DefaultedHandler<VariantsOfUnion<T, K>>,
typeKey?: K,
): ReturnType<Defined<typeof handler[keyof typeof handler]>> {
return match(obj, handler as any, typeKey) as any;
};


/**
* Match a variant against it's some of its possible options and do some
* processing based on the type of variant received. Finally, take the remaining
* possibilities and handle them in a function.
*
* The input to the 'or' clause is well-typed.
*
* @deprecated
* @param obj the variant in question
* @param handler an object whose keys are the type names of the variant's type and values are handler functions for each option.
* @param {string?} typeKey override the property to inspect. By default, 'type'.
* @returns {The union of the return types of the various branches of the handler object}
*/
export function matchElse<
T extends Property<K, string>,
H extends Partial<Handler<VariantsOfUnion<T, K>>>,
E extends (rest: Exclude<T, Property<K, keyof H>>) => any,
K extends string = 'type'
> (
obj: T,
handler: H,
_else: E,
typeKey?: K,
): ReturnType<Defined<H[keyof H]>> | ReturnType<E> {
const typeString = obj[typeKey ?? 'type' as K];
return handler[typeString]?.(obj as any) ?? _else(obj as any)
}
9 changes: 6 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ export {
Anonymous,
} from './nominal';

export * from './deprecated';

export {
augment,
AugmentVariant,
augmented,
cast,
constrainedVariant,
constrained,
isOfVariant,
keymap,
KeyMap,
Expand All @@ -18,6 +19,8 @@ export {
Matrix,
narrow,
outputTypes,
patterned,
Property,
TypeExt,
TypeNames,
variant,
Expand Down
6 changes: 3 additions & 3 deletions src/lookup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {WithProperty, VariantsOfUnion} from './variant';
import {Property, VariantsOfUnion} from './variant';

/**
* An object that has the same keys as a variant but has arbitrary values for the data.
Expand All @@ -15,7 +15,7 @@ export type Lookup<T, U = any> = {
* @param typeKey the key used as the discriminant.
*/
export function lookup<
T extends WithProperty<K, string>,
T extends Property<K, string>,
L extends Lookup<VariantsOfUnion<T, K>>,
K extends string = 'type'
>(obj: T, handler: L, typeKey?: K): L[keyof L] {
Expand All @@ -31,7 +31,7 @@ export function lookup<
* @param typeKey the key used as the discriminant.
*/
export function partialLookup<
T extends WithProperty<K, string>,
T extends Property<K, string>,
L extends Lookup<VariantsOfUnion<T, K>>,
K extends string = 'type'
>(obj: T, handler: Partial<L>, typeKey?: K): L | undefined {
Expand Down
97 changes: 29 additions & 68 deletions src/match.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import {Func} from './util';
import {TypeExt, UnionHandler, WithProperty, VariantsOfUnion, VariantModule, KeysOf, Variant} from './variant';
import {TypeExt, UnionHandler, VariantsOfUnion, VariantModule, KeysOf, Variant, Property} from './variant';


/**
* Strip undefined from a union of types.
*/
type Defined<T> = T extends undefined ? never : T;

/**
* Built to describe an object with the same keys as a variant but instead of constructors
Expand All @@ -15,15 +11,24 @@ export type Handler<T, U = any> = {
[P in keyof T]: (variant: T[P]) => U
}

/**
* Either the full handler or the partial set plus 'default'
*/
type WithDefault<T, U = any> =
| Partial<T> & {default: (...args: Parameters<FuncsOnly<T>[keyof T]>) => U}
| T
;

/**
* Pick just the functions of an object.
*/
type FuncsOnly<T> = {
[P in keyof T]: T[P] extends Func ? T[P] : never;
}

/**
* The key used to indicate the default handler.
*/
export const DEFAULT_KEY = 'default';
export type DEFAULT_KEY = typeof DEFAULT_KEY;

Expand All @@ -32,20 +37,31 @@ export type DEFAULT_KEY = typeof DEFAULT_KEY;
*/
export interface VariantError<T> { __error: never, __message: T };

/**
* Prevents 'overflow' in a literal.
*
* @todo this may be unnecessary if you use that 'splay partial' trick.
*/
export type Limited<T, U> = Exclude<keyof T, U> extends never
? T
: VariantError<['Expected keys of handler', keyof T, 'to be limited to possible keys', U]>
;

/**
* Strip undefined from a union of types.
*/
export type Defined<T> = T extends undefined ? never : T;


/**
* Match a variant against its possible options and do some processing
* based on the type of variant received.
* @param obj the variant in question
* @param handler an object whose keys are the type names of the variant's type and values are handler functions for each option.
* @returns {The union of the return types of the various branches of the handler object}
* @returns The union of the return types of the various branches of the handler object
*/
export function match<
T extends WithProperty<'type', string>,
T extends Property<'type', string>,
H extends WithDefault<Handler<VariantsOfUnion<T>>>,
>(obj: T, handler: H & Limited<H, T['type'] | DEFAULT_KEY>):
ReturnType<Limit<FuncsOnly<H>, T['type'] | DEFAULT_KEY>[keyof H]>;
Expand All @@ -55,10 +71,10 @@ export function match<
* @param obj the variant in question
* @param handler an object whose keys are the type names of the variant's type and values are handler functions for each option.
* @param {string?} typeKey override the property to inspect. By default, 'type'.
* @returns {The union of the return types of the various branches of the handler object}
* @returns The union of the return types of the various branches of the handler object
*/
export function match<
T extends WithProperty<K, string>,
T extends Property<K, string>,
H extends WithDefault<Handler<VariantsOfUnion<T, K>>>,
K extends string = 'type'
>(obj: T, handler: H & Limited<H, T[K] | DEFAULT_KEY>, typeKey?: K): ReturnType<Limit<FuncsOnly<H>, T[K] | DEFAULT_KEY>[keyof H]>;
Expand All @@ -75,7 +91,7 @@ K extends string = 'type'
* @returns {The union of the return types of the various branches of the handler object}
*/
export function match<
T extends WithProperty<K, string>,
T extends Property<K, string>,
H extends Partial<Handler<VariantsOfUnion<T, K>>>,
E extends (rest: Exclude<T, TypeExt<K, keyof H>>) => any,
K extends string = 'type'
Expand All @@ -84,7 +100,7 @@ export function match<
* Actual impl
*/
export function match<
T extends WithProperty<K, string>,
T extends Property<K, string>,
H extends Partial<Handler<VariantsOfUnion<T, K>>> | WithDefault<Handler<VariantsOfUnion<T, K>>>,
E extends (rest: Exclude<T, TypeExt<K, keyof H>>) => any,
K extends string = 'type'
Expand All @@ -106,63 +122,6 @@ export function match<
}
}


/**
* Built to describe an object with the same keys as a variant but instead of constructors
* for those objects has functions that handle objects of that type. In this case, the
* keys are all partial and there is an extra option "default"
*/
export type DefaultedHandler<T, U = any> = Partial<Handler<T> & {default?: (union: T[keyof T]) => U}>

type Keyless<T> = T & {key: never};
/**
* Match a variant against some of its possible options and do some
* processing based on the type of variant received. May return undefined
* if the variant is not accounted for by the handler.
* @param obj
* @param handler
* @param typeKey override the property to inspect. By default, 'type'.
*/
export function partialMatch<
T extends WithProperty<K, string>,
K extends string = 'type'
> (
obj: T,
handler: DefaultedHandler<VariantsOfUnion<T, K>>,
typeKey?: K,
): ReturnType<Defined<typeof handler[keyof typeof handler]>> {
return match(obj, handler as any, typeKey) as any;
};


/**
* Match a variant against it's some of its possible options and do some
* processing based on the type of variant received. Finally, take the remaining
* possibilities and handle them in a function.
*
* The input to the 'or' clause is well-typed.
*
* @param obj the variant in question
* @param handler an object whose keys are the type names of the variant's type and values are handler functions for each option.
* @param {string?} typeKey override the property to inspect. By default, 'type'.
* @returns {The union of the return types of the various branches of the handler object}
*/
export function matchElse<
T extends WithProperty<K, string>,
H extends Partial<Handler<VariantsOfUnion<T, K>>>,
E extends (rest: Exclude<T, TypeExt<K, keyof H>>) => any,
K extends string = 'type'
> (
obj: T,
handler: H,
_else: E,
typeKey?: K,
): ReturnType<Defined<H[keyof H]>> | ReturnType<E> {
const typeString = obj[typeKey ?? 'type' as K];
return handler[typeString]?.(obj as any) ?? _else(obj as any)
}


/**
* Match a literal against some of its possible options and do some processing based
* on the type of literal received. Works well with strEnum
Expand All @@ -173,6 +132,8 @@ export function matchLiteral<T extends string, H extends UnionHandler<T>>(litera
return handler[literal]?.(literal);
}

// I'm not sure if this is still necessary but I don't want to mess
// with the match types until I add more strict tests.
type Limit<T, Keys extends string> = {
[P in keyof T]: P extends Keys ? T[P] : never;
}
Expand Down
Loading

0 comments on commit ec65921

Please sign in to comment.