Skip to content

Commit

Permalink
Add support for filterIf and rejectIf
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanGoncharov committed May 27, 2017
1 parent 79d0844 commit e68d92a
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 124 deletions.
59 changes: 23 additions & 36 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ import each from 'lodash/each.js';
import keyBy from 'lodash/keyBy.js';
import isEqual from 'lodash/isEqual.js';

import {
transformations,
transformationToType
} from './transformations';
import { applyTransformations } from './transformations';

import { lodashIDL } from './lodash_idl';

Expand Down Expand Up @@ -73,16 +70,28 @@ function getLodashDirectiveArgs(node) {
}

function normalizeLodashArgs(argNodes, args) {
if (!argNodes)
return args;

//Restore order of arguments
argNodes = keyBy(argNodes, argNode => argNode.name.value);
const orderedArgs = {};
each(argNodes, (node, name) => {
if (lodashDirectiveArgTypes[name].name === 'DummyArgument')
const argValue = args[name];

if (node.value.kind === 'ObjectValue')
orderedArgs[name] = normalizeLodashArgs(node.value.fields, argValue);
else if (node.value.kind === 'ListValue') {
const nodeValues = node.value.values;

orderedArgs[name] = [];
for (let i = 0; i < nodeValues.length; ++i)
orderedArgs[name][i] = normalizeLodashArgs(nodeValues[i].fields, argValue[i]);
}
else if (node.value.kind === 'EnumValue' && node.value.value === 'none')
orderedArgs[name] = undefined;
else if (name === 'each')
orderedArgs[name] = normalizeLodashArgs(node.value.fields, args[name]);
else
orderedArgs[name] = args[name];
orderedArgs[name] = argValue;
});
return orderedArgs;
}
Expand All @@ -96,31 +105,13 @@ function applyLodashDirective(pathToArgs, data) {
}

function applyLodashArgs(path, object, args) {
if (!args)
return object;

for (const op in args) {
const arg = args[op];

const type = (op === 'each' ? 'Array' : transformationToType[op]);
let actualType = object.constructor && object.constructor.name;
// handle objects created with Object.create(null)
if (!actualType && (typeof object === 'object'))
actualType = 'Object';

if (type !== actualType) {
const pathStr = path.join('.');
throw Error(
`${pathStr}: "${op}" transformation expect "${type}" but got "${actualType}"`
);
}

if (op === 'each')
object = object.map((item, idx) => applyLodashArgs(path.concat(idx), item, arg));
else
object = transformations[type][op](object, arg);
try {
return applyTransformations(object, args);
} catch (e) {
// FIXME:
console.log(path);
throw e;
}
return object;
}

function applyOnPath(result, pathToArgs) {
Expand Down Expand Up @@ -163,10 +154,6 @@ function stripQuery(queryAST):DocumentNode {

export const lodashDirectiveAST:DocumentNode = parse(new Source(lodashIDL, 'lodashIDL'));
const lodashDirectiveDef = getDirectivesFromAST(lodashDirectiveAST)[0];
const lodashDirectiveArgTypes = lodashDirectiveDef.args.reduce((obj, arg) => {
obj[arg.name] = arg.type;
return obj;
}, {});

function getDirectivesFromAST(ast) {
const dummyIDL = `
Expand Down
108 changes: 27 additions & 81 deletions src/lodash_idl.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
export const lodashIDL = `
scalar Path
scalar JSON
enum DummyArgument {
none
}
directive @_(
const lodashProps = `
map: Path
keyBy: Path
each: LodashOperations
Expand Down Expand Up @@ -52,6 +44,8 @@ directive @_(
countBy: Path
filter: JSON
reject: JSON
filterIf: Predicate
rejectIf: Predicate
groupBy: Path
sortBy: [Path!]
Expand Down Expand Up @@ -81,82 +75,34 @@ directive @_(
keys: DummyArgument
# Creates an array of the own enumerable string keyed property values of object.
values: DummyArgument
) on FIELD | QUERY
input LodashOperations {
map: Path
keyBy: Path
each: LodashOperations
# Creates an array of elements split into groups the length of size.
# If array can't be split evenly, the final chunk will be the remaining elements.
chunk: Int
# Creates a slice of array with n elements dropped from the beginning.
drop: Int
# Creates a slice of array with n elements dropped from the end.
dropRight: Int
# Creates a slice of array with n elements taken from the beginning.
take: Int
# Creates a slice of array with n elements taken from the end.
takeRight: Int
# Recursively flatten array up to depth times.
flattenDepth: Int
# The inverse of \`toPairs\`; this method returns an object composed from key-value
# pairs.
fromPairs: DummyArgument
# Gets the element at index n of array. If n is negative, the nth element from
# the end is returned.
nth: Int
# Reverses array so that the first element becomes the last, the second element
# becomes the second to last, and so on.
reverse: DummyArgument
# Creates a duplicate-free version of an array, in which only the first occurrence
# of each element is kept. The order of result values is determined by the order
# they occur in the array.
uniq: DummyArgument
uniqBy: Path
countBy: Path
filter: JSON
reject: JSON
groupBy: Path
sortBy: [Path!]
minBy: Path
maxBy: Path
meanBy: Path
sumBy: Path
`;

# Converts all elements in array into a string separated by separator.
join: String
export const lodashIDL = `
scalar Path
scalar JSON
get: Path
mapValues: Path
enum DummyArgument {
none
}
# Creates an array of values corresponding to paths of object.
at: [Path!]
# Creates an array of own enumerable string keyed-value pairs for object.
toPairs: DummyArgument
input Predicate {
lt: JSON
lte: JSON
gt: JSON
gte: JSON
eq: JSON
startsWith: String
endsWith: String
and: [Predicate!]
or: [Predicate!]
${lodashProps}
}
# Creates an object composed of the inverted keys and values of object.
# If object contains duplicate values, subsequent values overwrite property
# assignments of previous values.
invert: DummyArgument
directive @_(
${lodashProps}
) on FIELD | QUERY
invertBy: Path
# Creates an array of the own enumerable property names of object.
keys: DummyArgument
# Creates an array of the own enumerable string keyed property values of object.
values: DummyArgument
input LodashOperations {
${lodashProps}
}
`;
73 changes: 66 additions & 7 deletions src/transformations.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import every from 'lodash/every.js';
import some from 'lodash/some.js';
import startsWith from 'lodash/startsWith.js';
import endsWith from 'lodash/endsWith.js';
import lt from 'lodash/lt.js';
import lte from 'lodash/lte.js';
import gt from 'lodash/gt.js';
import gte from 'lodash/gte.js';
import eq from 'lodash/eq.js';
import map from 'lodash/map.js';
import keyBy from 'lodash/keyBy.js';
import chunk from 'lodash/chunk.js';
Expand Down Expand Up @@ -31,8 +40,11 @@ import invertBy from 'lodash/invertBy.js';
import keys from 'lodash/keys.js';
import values from 'lodash/values.js';

export const transformations = {
const transformations = {
Array: {
each: (array, arg) => {
return map(array, item => applyTransformations(item, arg));
},
map,
keyBy,
chunk,
Expand All @@ -49,6 +61,12 @@ export const transformations = {
countBy,
filter,
reject,
filterIf: (array, arg) => {
return filter(array, item => applyTransformations(item, arg));
},
rejectIf: (array, arg) => {
return reject(array, item => applyTransformations(item, arg));
},
groupBy,
sortBy,
minBy,
Expand All @@ -66,13 +84,54 @@ export const transformations = {
invertBy,
keys,
values,
}
},
Number: {
lt,
lte,
gt,
gte,
eq,
},
String: {
startsWith,
endsWith,
},
};

export const transformationToType = {};
for (const type in transformations) {
for (const name in transformations[type]) {
transformationToType[name] = type;
const opToExpectedType = {};
for (const type in transformations)
for (const name in transformations[type])
opToExpectedType[name] = type;

export function applyTransformations(object, args) {
if (!args)
return object;

for (const op in args) {
if (object === null)
break;

const arg = args[op];

if (op === 'and') {
object = every(arg, predicateArgs => applyTransformations(object, predicateArgs));
continue;
}
if (op === 'or') {
object = some(arg, predicateArgs => applyTransformations(object, predicateArgs));
continue;
}

const expectedType = opToExpectedType[op];
let type = object.constructor && object.constructor.name;
// handle objects created with Object.create(null)
if (!type && (typeof object === 'object'))
type = 'Object';

if (expectedType !== type)
throw Error(`"${op}" transformation expect "${expectedType}" but got "${type}"`);

object = transformations[type][op](object, arg);
}
return object;
}

0 comments on commit e68d92a

Please sign in to comment.