aws-amplify / amplify-category-api

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development. This plugin provides functionality for the API category, allowing for the creation and management of GraphQL and REST based backends for your amplify project.
https://docs.amplify.aws/
Apache License 2.0
81 stars 71 forks source link

Appsync simulator bug on batchinvoke pipeline using different resolvers (serverless-appsync-simulator) #525

Open gianpaolopascasidc opened 2 years ago

gianpaolopascasidc commented 2 years ago

Before opening, please confirm:

How did you install the Amplify CLI?

npm (using serverless-appsync-simulator)

If applicable, what version of Node.js are you using?

14.18.3

Amplify CLI Version

amplify-appsync-simulator 2.4.0

What operating system are you using?

MacOS

Did you make any manual changes to the cloud resources managed by Amplify? Please describe the changes made.

Using a local setup to simulate the AWS env

Amplify Categories

function

Amplify Commands

Not applicable

Describe the bug

Premise: on production it's all working good. I have setup a docker machine using serverless offline and serverless-appsync-simulator (a wrapper of amplify-appsync-simualtor). When you invoke a GraphQL query (a pipeline) which triggers different resolvers the simulator will give the following error:

TypeError: Cannot read property 'info' of undefined
at LambdaDataLoader.<anonymous> (/var/task/node_modules/amplify-appsync-simulator/lib/data-loader/lambda/index.js:64:40)
at step (/var/task/node_modules/amplify-appsync-simulator/lib/data-loader/lambda/index.js:33:23)
at Object.next (/var/task/node_modules/amplify-appsync-simulator/lib/data-loader/lambda/index.js:14:53)
at /var/task/node_modules/amplify-appsync-simulator/lib/data-loader/lambda/index.js:8:71
at new Promise (<anonymous>)
at __awaiter (/var/task/node_modules/amplify-appsync-simulator/lib/data-loader/lambda/index.js:4:12)
at LambdaDataLoader.load (/var/task/node_modules/amplify-appsync-simulator/lib/data-loader/lambda/index.js:56:16)
at AmplifySimulatorFunction.<anonymous> (/var/task/node_modules/amplify-appsync-simulator/lib/resolvers/function.js:102:57)
at step (/var/task/node_modules/amplify-appsync-simulator/lib/resolvers/function.js:48:23)
at Object.next (/var/task/node_modules/amplify-appsync-simulator/lib/resolvers/function.js:29:53)
at fulfilled (/var/task/node_modules/amplify-appsync-simulator/lib/resolvers/function.js:20:58)
at processTicksAndRejections (internal/process/task_queues.js:97:5)

So, as on the production env is all working I have started going around the library code and i located some suspect code (and i tried fixing it and it works). I will explain what problems i found and what solutions i applied. The first problem i discovered is that in LambaDataLoader (amplify-appsync-simulator/lib/data-loader/lambda/index.ts) in load(req, extraData) the extraData is always destructured but in this case the function resolver (amplify-appsync-simulator/lib/resolvers/function.js:40) calls the load(req, extraData) method omitting the extraData argument. Let's say the solution is using dataLoader.load(requestTemplateResult.result, { info }) (info is correctly defined in the scope of resolve()) instead of dataLoader.load(requestTemplateResult.result). Once you have info to destructure from extraData in LambdaDataLoader.load() it seems that the function getBatchDataResolver() "caches" a resolver (not yet invoked) using the parentType + queryField as key. The result of this behaviour is that the first defined resolver in the pipeline will be invoked for every function to execute (even if it is defined in another resolver). Finally, i found the solution to solve this assigning the resolver name as cache key. So i changed the line from: const batchName = "${parentType}.${fieldName}"; to: const batchName = this._config.name;

Now, i don't know if it was made on purpose or it is an edge case bug. I really hope this can be a solution and if you think this can work i can open a PR 😄

Expected behavior

All the pipeline is executed invoking the right resolver for every function called.

Reproduction steps

You have to invoke a query using a pipeline with functions from different resolvers.

GraphQL schema(s)

```graphql # Put schemas below this line ```

Log output

``` # Put your logs below this line ```

Additional information

No response

josefaidt commented 2 years ago

Hey @gianpaolopascasidc :wave: thanks for raising this! I'm going to transfer this over to our API repo for visibility and better assistance 🙂

Cox65 commented 1 year ago

Any update on this issue? We had to fix it for our projects and actually the fix is (as you can have a pipeline of lambda data sources) :

In function.ts

try {
      result = await dataLoader.load(requestTemplateResult.result, {info});
    } catch (e) {

In lambda/index.ts

const { fieldName, parentType } = extraData.info;
const batchName = `${this._config.name}.${parentType.name}.${fieldName}`;