From ce542203f2d18465549654f6566ddc3808e28dad Mon Sep 17 00:00:00 2001 From: brianjquinn Date: Thu, 21 Jan 2021 16:14:05 -0800 Subject: [PATCH] fix/support passing Array args to delegateToComponent's options.args object --- lib/delegate/__tests__.js | 53 ++++++++++++++++++++++++++++----------- lib/delegate/index.js | 25 +++++++----------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/lib/delegate/__tests__.js b/lib/delegate/__tests__.js index 421de5e..f917fb6 100644 --- a/lib/delegate/__tests__.js +++ b/lib/delegate/__tests__.js @@ -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 { @@ -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'}]; } } @@ -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; diff --git a/lib/delegate/index.js b/lib/delegate/index.js index 672d41c..8f1f5cf 100644 --- a/lib/delegate/index.js +++ b/lib/delegate/index.js @@ -5,7 +5,6 @@ const { subscribe, isAbstractType, isObjectType, - getNamedType, print, astFromValue, coerceInputValue, @@ -87,7 +86,7 @@ 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) { @@ -95,22 +94,16 @@ const createSubOperationDocument = function (component, targetRootField, args, s } } - // 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 },