-
-
Notifications
You must be signed in to change notification settings - Fork 2
Completed analysis of include and skip directives #106
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
base: dev
Are you sure you want to change the base?
Changes from 2 commits
fc3d616
fddd78a
9d01f85
f135be4
cb261a4
0ccd91e
9efe334
3401795
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,10 +22,10 @@ import { FieldWeight, TypeWeightObject, Variables } from '../@types/buildTypeWei | |
* |-----> Selection Set Node <-------| | ||
* | / | ||
* | Selection Node | ||
* | (Field, Inline fragment and fragment spread) | ||
* | | | \ | ||
* | Field Node | fragmentCache | ||
* | | | | ||
* | (Field, Inline fragment, directives and fragment spread) | ||
* | | | \ \ | ||
* | Field Node | \ \ | ||
* | | | directiveCheck fragmentCache | ||
* |<--calculateCast | | ||
* | | | ||
* |<------------------| | ||
|
@@ -151,11 +151,16 @@ class QueryParser { | |
* 1. there is no directive | ||
* 2. there is a directive named inlcude and the value is true | ||
* 3. there is a directive named skip and the value is false | ||
* 4. there is a directive of any other form | ||
*/ | ||
// THIS IS NOT CALLED ANYWEHERE. IN PROGRESS | ||
private directiveCheck(directive: DirectiveNode): boolean { | ||
if (directive?.arguments) { | ||
// get the first argument | ||
// check if there is a directive "include" or "skip" and arguments are present | ||
// evaluate the state of the directive to ignore or calculate the compexity of this queried field | ||
if ( | ||
directive?.arguments && | ||
(directive.name.value === 'include' || directive.name.value === 'skip') | ||
) { | ||
// only consider the first argument | ||
const argument = directive.arguments[0]; | ||
// ensure the argument name is 'if' | ||
const argumentHasVariables = | ||
|
@@ -173,6 +178,7 @@ class QueryParser { | |
(directive.name.value === 'skip' && directiveArgumentValue === false) | ||
); | ||
} | ||
// return true to process all queried fields without/other directives | ||
return true; | ||
} | ||
|
||
|
@@ -185,42 +191,44 @@ class QueryParser { | |
* 2. there is a directive named inlcude and the value is true | ||
* 3. there is a directive named skip and the value is false | ||
*/ | ||
// const directive = node.directives; | ||
// if (directive && this.directiveCheck(directive[0])) { | ||
this.depth += 1; | ||
if (this.depth > this.maxDepth) this.maxDepth = this.depth; | ||
// the kind of a field node will either be field, fragment spread or inline fragment | ||
if (node.kind === Kind.FIELD) { | ||
complexity += this.fieldNode(node, parentName.toLowerCase()); | ||
} else if (node.kind === Kind.FRAGMENT_SPREAD) { | ||
// add complexity and depth from fragment cache | ||
const { complexity: fragComplexity, depth: fragDepth } = | ||
this.fragmentCache[node.name.value]; | ||
complexity += fragComplexity; | ||
this.depth += fragDepth; | ||
const directive = node.directives; | ||
if (directive && this.directiveCheck(directive[0])) { | ||
this.depth += 1; | ||
if (this.depth > this.maxDepth) this.maxDepth = this.depth; | ||
this.depth -= fragDepth; | ||
// the kind of a field node will either be field, fragment spread or inline fragment | ||
if (node.kind === Kind.FIELD) { | ||
complexity += this.fieldNode(node, parentName.toLowerCase()); | ||
} else if (node.kind === Kind.FRAGMENT_SPREAD) { | ||
// add complexity and depth from fragment cache | ||
const { complexity: fragComplexity, depth: fragDepth } = | ||
this.fragmentCache[node.name.value]; | ||
complexity += fragComplexity; | ||
this.depth += fragDepth; | ||
if (this.depth > this.maxDepth) this.maxDepth = this.depth; | ||
this.depth -= fragDepth; | ||
|
||
// This is a leaf | ||
// need to parse fragment definition at root and get the result here | ||
} else if (node.kind === Kind.INLINE_FRAGMENT) { | ||
const { typeCondition } = node; | ||
|
||
// This is a leaf | ||
// need to parse fragment definition at root and get the result here | ||
} else if (node.kind === Kind.INLINE_FRAGMENT) { | ||
const { typeCondition } = node; | ||
// named type is the type from which inner fields should be take | ||
// If the TypeCondition is omitted, an inline fragment is considered to be of the same type as the enclosing context | ||
const namedType = typeCondition | ||
? typeCondition.name.value.toLowerCase() | ||
: parentName; | ||
|
||
// named type is the type from which inner fields should be take | ||
// If the TypeCondition is omitted, an inline fragment is considered to be of the same type as the enclosing context | ||
const namedType = typeCondition ? typeCondition.name.value.toLowerCase() : parentName; | ||
// TODO: Handle directives like @include and @skip | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this |
||
// subtract 1 before, and add one after, entering the fragment selection to negate the additional level of depth added | ||
this.depth -= 1; | ||
complexity += this.selectionSetNode(node.selectionSet, namedType); | ||
this.depth += 1; | ||
} else { | ||
throw new Error(`ERROR: QueryParser.selectionNode: node type not supported`); | ||
} | ||
|
||
// TODO: Handle directives like @include and @skip | ||
// subtract 1 before, and add one after, entering the fragment selection to negate the additional level of depth added | ||
this.depth -= 1; | ||
complexity += this.selectionSetNode(node.selectionSet, namedType); | ||
this.depth += 1; | ||
} else { | ||
throw new Error(`ERROR: QueryParser.selectionNode: node type not supported`); | ||
} | ||
|
||
this.depth -= 1; | ||
//* } | ||
return complexity; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -855,7 +855,7 @@ describe('Test getQueryTypeComplexity function', () => { | |
}); | ||
|
||
// TODO: refine complexity analysis to consider directives includes and skip | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this TODO |
||
xdescribe('with directives @includes and @skip', () => { | ||
describe('with directives @includes and @skip', () => { | ||
test('@includes on interfaces', () => { | ||
query = ` | ||
query { | ||
|
@@ -871,10 +871,10 @@ describe('Test getQueryTypeComplexity function', () => { | |
} | ||
} | ||
}`; | ||
expect(mockCharacterFriendsFunction).toHaveBeenCalledTimes(0); | ||
mockCharacterFriendsFunction.mockReturnValueOnce(3); | ||
// Query 1 + 1 hero + max(...Character 3, ...Human 0) = 5 | ||
expect(queryParser.processQuery(parse(query))).toBe(5); | ||
|
||
mockCharacterFriendsFunction.mockReset(); | ||
query = ` | ||
query { | ||
hero(episode: EMPIRE) { | ||
|
@@ -889,7 +889,7 @@ describe('Test getQueryTypeComplexity function', () => { | |
} | ||
} | ||
}`; | ||
mockCharacterFriendsFunction.mockReturnValueOnce(3); | ||
expect(mockCharacterFriendsFunction).toHaveBeenCalledTimes(0); | ||
// Query 1 + 1 hero = 2 | ||
expect(queryParser.processQuery(parse(query))).toBe(2); | ||
}); | ||
|
@@ -912,7 +912,7 @@ describe('Test getQueryTypeComplexity function', () => { | |
expect(mockCharacterFriendsFunction).toHaveBeenCalledTimes(0); | ||
// Query 1 + 1 hero = 2 | ||
expect(queryParser.processQuery(parse(query))).toBe(2); | ||
|
||
mockCharacterFriendsFunction.mockReset(); | ||
query = ` | ||
query { | ||
hero(episode: EMPIRE) { | ||
|
@@ -987,7 +987,8 @@ describe('Test getQueryTypeComplexity function', () => { | |
// 1 query + 1 hero + 1 human | ||
expect(queryParser.processQuery(parse(query))).toBe(3); | ||
}); | ||
test('with arguments and varibales', () => { | ||
|
||
test('@skip with arguments and varibales', () => { | ||
variables = { directive: false }; | ||
queryParser = new QueryParser(typeWeights, variables); | ||
query = `query ($directive: Boolean!){ | ||
|
@@ -1008,17 +1009,48 @@ describe('Test getQueryTypeComplexity function', () => { | |
hero(episode: EMPIRE) { | ||
id, name | ||
} | ||
human(id: 1) @includes(if: $directive) { | ||
human(id: 1) @skip(if: $directive) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The weight of Human and hero are both 1. Can you add something that differentiates the weights so we know that this is working. |
||
id, | ||
name, | ||
homePlanet | ||
} | ||
}`; | ||
// 1 query + 1 hero | ||
expect(queryParser.processQuery(parse(query))).toBe(2); | ||
}); | ||
|
||
test('@includes with arguments and varibales', () => { | ||
variables = { directive: false }; | ||
queryParser = new QueryParser(typeWeights, variables); | ||
query = `query ($directive: Boolean!){ | ||
hero(episode: EMPIRE) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. Differentiate the weights of |
||
id, name | ||
} | ||
human(id: 1) @include(if: $directive) { | ||
id, | ||
name, | ||
homePlanet | ||
} | ||
}`; | ||
// 1 query + 1 hero | ||
expect(queryParser.processQuery(parse(query))).toBe(2); | ||
variables = { directive: true }; | ||
queryParser = new QueryParser(typeWeights, variables); | ||
query = `query ($directive: Boolean!){ | ||
hero(episode: EMPIRE) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here as well |
||
id, name | ||
} | ||
human(id: 1) @include(if: $directive) { | ||
id, | ||
name, | ||
homePlanet | ||
} | ||
}`; | ||
// 1 query + 1 hero + 1 human | ||
expect(queryParser.processQuery(parse(query))).toBe(3); | ||
}); | ||
|
||
xtest('and other directive are ignored', () => { | ||
test('and other directives or arguments are ignored', () => { | ||
query = `query { | ||
hero(episode: EMPIRE) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Differentiate weights here as well |
||
id, name | ||
|
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.
We should also handle cases where there are multiple directives attached to this node rather than just checking the first directive.
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've added this. What is your take on this check:
node.directives
is always truthy but TS requires the check in order to access the array value atnode.directives
.