-
Couldn't load subscription status.
- Fork 0
Handle more advanced input queries #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if there is a simpler overall approach that will eliminate a lot of this complexity. Can we not just traverse the input query AST alongside the JSON value and validate as we go?
| // Match alias pattern: word followed by colon and whitespace and another word | ||
| // But only when followed by whitespace, opening brace, or closing brace | ||
| // This avoids matching colons inside strings or arguments | ||
| return graphqlString.replace(/\b(\w+):\s*(\w+)(?=[\s{},])/g, '$2'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like it has the potential to miss a number of edge cases. I would much prefer if we could manipulate the AST instead of doing regex operations.
Additionally, how do we expect to handle the case of the same field selected multiple times with aliases? For example:
{
discountNode {
metafield1: metafield(key: "my-key1", namespace: "my-namespace") { jsonValue }
metafield2: metafield(key: "my-key2", namespace: "my-namespace") { jsonValue }
}
}
src/utils/build-query-field-info.ts
Outdated
| if (selection.kind === 'Field') { | ||
| const field = selection as FieldNode; | ||
| const actualFieldName = field.name.value; | ||
| const aliasName = field.alias?.value || actualFieldName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: it's not an alias name if we fallback to actualFieldName. I would call it responseKey or something like that.
b1009c4 to
cc4beb1
Compare
… union/interface type detection
| if (queryOperation.variableDefinitions && queryOperation.variableDefinitions.length > 0) { | ||
| const varDefs = queryOperation.variableDefinitions.map(varDef => { | ||
| const varName = varDef.variable.name.value; | ||
| const varType = print(varDef.type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a print?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the print function from graphqljs. We're using it to convert from AST for to a consistent string format. Example:
// Variable definition in query AST:
// $itemId: ID!
varDef.variable.name.value // => "itemId"
varDef.type // => NonNullType { type: NamedType { name: "ID" } }
print(varDef.type) // => "ID!"
| if (fieldNode.arguments && fieldNode.arguments.length > 0) { | ||
| const args = fieldNode.arguments.map(arg => { | ||
| const argName = arg.name.value; | ||
| const argValue = print(arg.value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same. Here's another example with args:
// Field with arguments:
// metafield(namespace: "$app:config", key: "setting1", limit: 10, enabled: true, id: $itemId)
arg.name.value // => "namespace"
arg.value // => StringValue { value: "$app:config" }
print(arg.value) // => "\"$app:config\"" (with quotes!)
arg.name.value // => "limit"
arg.value // => IntValue { value: 10 }
print(arg.value) // => "10"
arg.name.value // => "enabled"
arg.value // => BooleanValue { value: true }
print(arg.value) // => "true"
arg.name.value // => "id"
arg.value // => Variable { name: { value: "itemId" } }
print(arg.value) // => "$itemId"
// Result: "metafield(namespace: \"$app:config\", key: "setting1", limit: 10, enabled: true, id: $itemId)"
0a04b23 to
e5590c1
Compare
| * - expectedOutput: Object - The output data from payload.output | ||
| * - expectedOutput: Object - The output data from payload.output | ||
| * - target: string - The target string from payload.target | ||
| * - inputQueryVariables: Object (optional) - Variable values from payload.inputQueryVariables |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need variable values? As a user I would be very confused because the variable values wouldn't affect the execution at all given that I'm also providing the result of the input query?
| // Calculate paths correctly for when used as a dependency: | ||
| // __dirname = /path/to/function/tests/node_modules/function-testing-helpers/src/methods | ||
| // Go up 5 levels to get to function directory: ../../../../../ = /path/to/function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this always the case? Is there not a scenario with workspaces where the dependencies are downloaded higher up in the hierarchy?
| if (wasmPath) { | ||
| // Use user-provided WASM path | ||
| resolvedWasmPath = path.resolve(wasmPath); | ||
| if (!fs.existsSync(resolvedWasmPath)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it standard to use these sync functions in an async function?
| } | ||
| } else { | ||
| // Check Rust build output location | ||
| const rustWasmPath = path.join(functionDir, 'target', 'wasm32-wasip1', 'release', `${functionName}.wasm`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another case where workspaces might complicate things. It's also not a guarantee that they are using wasm32-wasip1. We are starting to move away from this with the latest version of the shopify_function Rust crate to get off of wasi.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this change be split into a different PR? It seems like switching to invoke function-runner directly is not related to handling more advanced input queries?
| } | ||
|
|
||
| /** | ||
| * Run a function by calling function-runner directly (bypassing Shopify CLI) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets not include this yet because we dont get usage analytics if we use function-runner directly.
|
Sorry I should have posted about the runFunction direct changes. I just added those on top of this to have a faster way to iterate through tests since going through the CLI invocation was taking too much time. I should have branched off of this and kept that change separate. For now, I've put this back into draft as I revisit things after discussing with @adampetro . |
|
Closing in favour of the visitor approach that was merged here: #26 |
Fixes https://github.com/shop/issues-shopifyvm/issues/660
This PR reworks validation in order to support more graphql query features, such as aliasing, variables, fragments.
I also ended up reorganizing our test directory.
The fixture validation has been refactored into two functions
validateFixtureInputStructure(queryAST, schema, fixtureData)Approach: Parallel Tree Traversal
Recursively walks through the query AST and fixture data simultaneously, comparing structure at each level:
validateFixtureInputTypes(generatedQuery, schema, fixtureData, variables?)
Validates that the fixture's types match the schema by executing the query against fixture data:
Approach: GraphQL Execution Engine
Executes the normalized query against fixture data using GraphQL's built-in type validation.