import { curry, toPairs, pipe, chain, reduce, // map, head, keys, pick, always, prop, has } from 'ramda'; import { Option, fromNullable } from 'fp-ts/lib/Option'; import { map, // reduce, findFirst, filter, // head, array } from 'fp-ts/lib/Array'; export type Predicate = (args: item) => boolean; export type ObjectLiteral = { [key: string]: any } export type Matcher = (key: string, value: any) => boolean export type Conditions = Record export type Consumer = (original: ObjectLiteral, initialResult: result) => result export type Transformer = (result: result) => (key: string, value: any) => result export type Interpreters = { // Not sure if this constraint is even working properly tbh [K in keyof matchers]: Transformer } const hasPropIn = (original: ObjectLiteral) => (key: string) => has(key, original) const getPropFrom = (original: ObjectLiteral) => (key: string) => fromNullable(prop(key, original)); const findMaybe = (predicate: Predicate) => (original: item[]): Option => findFirst(predicate)(original) const pickIntersecting = pipe( keys, pick ) // Combines the predicates from Conditions with the transforming functions from interpreters // to create a full parser. Keys in the `typeHandlers` object should be a subset of keys from // the `typeConditions` object. const generateTypeMatcher = (typeConditions: typeConditions, typeHandlers: Interpreters) => { const handledConditions = pickIntersecting(typeHandlers)(typeConditions); const handledConditionPairs = toPairs(handledConditions); return ( (key: string, value: any): Option => { const matchedTypes = findMaybe<[string, any]>( ([typeName, predicate]) => predicate(key, value) )(handledConditionPairs); return ( matchedTypes .map(head) ); } ) } const matchTypeWithHandler = (handlers: ObjectLiteral) => (key: string) => fromNullable(prop(key, handlers)); const handleDefault: Transformer = (result) => (key, value) => result; const createParser = (typeConditions: typeConditions) => { const createConsumer = (typeHandlers: Interpreters): Consumer => { const findMatchingTypes = generateTypeMatcher(typeConditions, typeHandlers); const consumer: Consumer = (original: ObjectLiteral, initialResult: result) => { const originalPairs = toPairs(original); const createResultFromPairs = reduce((result: result, [key, value]) => { const parseItem = ( findMatchingTypes(key, value) .chain(getPropFrom(typeHandlers)) .getOrElse(handleDefault) ) return parseItem(result)(key, value); }, initialResult); return createResultFromPairs(originalPairs); } return consumer; } return createConsumer; } export default createParser;