Skip to content

Commit

Permalink
Merge pull request #84 from brianjquinn/delegate-user-provided-args-a…
Browse files Browse the repository at this point in the history
…rrays

fix/support passing Array args to delegateToComponent's options.args object
  • Loading branch information
brianjquinn authored Jan 22, 2021
2 parents 278d0ff + ce54220 commit 6a5b5d9
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 31 deletions.
53 changes: 38 additions & 15 deletions lib/delegate/__tests__.js
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,7 @@ Test('delegateToComponent - case 4 - delegateToComponent caller provides all arg
});

Test('delegateToComponent - user provided args of various types: ID (as Int), ID (as string), String, Int, Float, Boolean, enum, input object are passed', async (t) => {

const reviewsComponent = new GraphQLComponent({
types: `
type Review {
Expand All @@ -1457,28 +1458,43 @@ Test('delegateToComponent - user provided args of various types: ID (as Int), ID
type Query {
reviewsByPropertyId(
id: ID!
anotherId: ID!
intID: ID!
stringID: ID!
bool: Boolean!
int: Int!
float: Float!
string: String!
status: Status!
dates: Dates!): [Review]
dates: Dates!
arrayOfIntID: [ID!]!
arrayOfStringID: [ID!]!
arrayOfInt: [Int!]!
arrayOfFloat: [Float!]!
arrayOfString: [String!]!
arrayOfEnum: [Status!]!
arrayOfObj: [Dates!]!
): [Review]
}
`,
resolvers: {
Query: {
reviewsByPropertyId(_root, args) {
t.equals(Object.keys(args).length, 8, 'exactly 8 args passed');
t.equals(args.id, '2', 'id ID arg (passed as number) from delegateToComponent call passed');
t.equals(args.anotherId, '9', 'anotherId ID (passed as string) from delegateToComponent call passed');
t.equals(args.bool, true, 'bool Boolean arg from delegateToComponent call passed');
t.equals(args.int, 101, 'int Int arg from delegateToComponent call passed');
t.equals(args.float, 49.2, 'float Float arg from delegateToComponent passed');
t.equals(args.string, 'foobar', 'string String arg from delegateToComponent call passed');
t.equals(args.status, 'COMPLETE', 'status enum arg from delegateToComponent call passed');
t.deepEqual(args.dates, {from: 'from-date', to: 'to-date'}, 'dates input object arg from delegateToComponent call passed');
t.equals(Object.keys(args).length, 15, 'exactly 15 args passed');
t.equals(args.intID, '2', 'intID arg from delegateToComponent coerced and passed through as expected');
t.equals(args.stringID, '9', 'stringID arg from delegateToComponent coerced and passed through as expected');
t.equals(args.bool, true, 'bool arg from delegateToComponent coerced and passed through as expected');
t.equals(args.int, 101, 'int arg from delegateToComponent coerced and passed through as expected');
t.equals(args.float, 49.2, 'float arg from delegateToComponent coerced and passed through as expected');
t.equals(args.string, 'foobar', 'string arg from delegateToComponent coerced and passed through as expected');
t.equals(args.status, 'COMPLETE', 'status arg from delegateToComponent coerced and passed through as expected');
t.deepEqual(args.dates, { from: 'from-date', to: 'to-date' }, 'dates arg from delegateToComponent coerced and passed through as expected');
t.deepEqual(args.arrayOfIntID, ['1', '2'], 'arrayOfIDInt arg from delegateToComponent coerced and passed through as expected');
t.deepEqual(args.arrayOfStringID, ['3a', '4a'], 'arrayOfIDString arg from delegateToComponent coerced and passed through as expected');
t.deepEqual(args.arrayOfInt, [5, 6], 'arrayOfInt arg from delegateToComponent coerced and passed through as expected');
t.deepEqual(args.arrayOfFloat, [7.0, 8.0], 'arrayOfFloat arg from delegateToComponent coerced and passed through as expected');
t.deepEqual(args.arrayOfString, ['hello', 'goodbye'], 'arrayOfString arg from delegateToComponent coerced and passed through as expected');
t.deepEqual(args.arrayOfEnum, ['COMPLETE', 'PENDING'], 'arrayOfEnum arg from delegateToComponent coerced and passed through as expected');
t.deepEqual(args.arrayOfObj, [{ from: 'from-date-1', to: 'to-date-1' }, { from: 'from-date-2', to: 'to-date-2' }], 'arrayOfObj arg from delegateToComponent coerced and passed through as expected');
return [{ id: 'revid', content: 'some review content'}];
}
}
Expand Down Expand Up @@ -1509,14 +1525,21 @@ Test('delegateToComponent - user provided args of various types: ID (as Int), ID
contextValue: context,
targetRootField: 'reviewsByPropertyId',
args: {
id: 2,
anotherId: '9',
intID: 2,
stringID: '9',
bool: true,
int: 101,
float: 49.2,
string: 'foobar',
status: 'COMPLETE',
dates: { from: 'from-date', to: 'to-date' }
dates: { from: 'from-date', to: 'to-date' },
arrayOfIntID: [1, 2],
arrayOfStringID: ['3a', '4a'],
arrayOfInt: [5, 6],
arrayOfFloat: [7.0, 8.0],
arrayOfString: ['hello', 'goodbye'],
arrayOfEnum: ['COMPLETE', 'PENDING'],
arrayOfObj: [{ from: 'from-date-1', to: 'to-date-1'}, {from: 'from-date-2', to: 'to-date-2'}]
}
});
return reviews;
Expand Down
25 changes: 9 additions & 16 deletions lib/delegate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const {
subscribe,
isAbstractType,
isObjectType,
getNamedType,
print,
astFromValue,
coerceInputValue,
Expand Down Expand Up @@ -87,30 +86,24 @@ const createSubOperationDocument = function (component, targetRootField, args, s
const targetRootFieldArguments = [];
// skip argument processing if the target root field doesn't have any arguments
if (definedRootFieldArgs.length > 0) {
// get the calling resolver's arguments

const callingResolverArgs = [];
for (const fieldNode of info.fieldNodes) {
if (fieldNode.arguments && fieldNode.arguments.length > 0) {
callingResolverArgs.push(...fieldNode.arguments);
}
}

// for each argument defined for the target root field
// check if the caller of delegateToComponent provided an argument of
// the same name (and type) and forward it on if so
// if not - check the calling resolver's args for an argument of the
// same name and forward it if so.
for (const definedArg of definedRootFieldArgs) {
// a caller of delegateToComponent provided an argument that matches
// the target root field's argument name
if (args[definedArg.name]) {
const definedArgNamedType = getNamedType(definedArg.type);
// this provides us some type safety by trying to coerce the user's
// argument value to the type defined by the target field's matching
// argument - if they dont match, it will throw a meaningful error.
// without this astFromValue would coerce things we dont want coerced
const coercedArgValue = coerceInputValue(args[definedArg.name], definedArgNamedType);
const argValueNode = astFromValue(coercedArgValue, definedArgNamedType);
// coerceInputValue: https://github.com/graphql/graphql-js/blob/v14.7.0/src/utilities/coerceInputValue.js
// is used to take the JS value provided by the caller of
// delegateToComponent and coerce it to the JS value associated with
// the type of the associated GraphQL argument - this will throw an
// error if there is a type mismatch - you wont know until query
// execution time if an error will occur here
const coercedArgValue = coerceInputValue(args[definedArg.name], definedArg.type);
const argValueNode = astFromValue(coercedArgValue, definedArg.type);
targetRootFieldArguments.push({
kind: Kind.ARGUMENT,
name: { kind: Kind.NAME, value: definedArg.name },
Expand Down

0 comments on commit 6a5b5d9

Please sign in to comment.