-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Support for @skip
and @include
#275
Changes from all commits
d89d14d
948618d
f53939f
5588db1
46dab91
7e1395a
28b9a41
ca7518b
8870462
f84f45f
20c1ea4
7cd95d5
0c1e74c
e2b410d
1cd9f9f
bced2d4
4bda736
a451fd2
5df4a87
132f2e7
85a7585
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 |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Provides the methods that allow QueryManager to handle | ||
// the `skip` and `include` directives within GraphQL. | ||
import { | ||
Selection, | ||
Variable, | ||
BooleanValue, | ||
} from 'graphql'; | ||
|
||
|
||
export function shouldInclude(selection: Selection, variables?: { [name: string]: any }): Boolean { | ||
if (!variables) { | ||
variables = {}; | ||
} | ||
|
||
if (!selection.directives) { | ||
return true; | ||
} | ||
|
||
let res: Boolean = true; | ||
selection.directives.forEach((directive) => { | ||
// TODO should move this validation to GraphQL validation once that's implemented. | ||
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. I don't think we should ever validate on the client, but maybe as a static analysis step. 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. I think client-side validation is going to be a thing no matter what. Maybe not in the next 6 months, but it's certainly a useful feature we should eventually have. 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. I don't see why we would want client-side validation at runtime, that seems like just adding unnecessary code to the client. 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. I don't mean validation that you can do at compile-time, but validation on input values etc., which I think we'll add through schema directives. 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. Hmm, so I guess given the variables and the query we'll try to figure out if they work, and if not don't send the query? I guess that could be good, so that you can save on server resources. I would be a bit worried about validation being inconsistent on client and server but we can cross that bridge when we get there. |
||
if (directive.name.value !== 'skip' && directive.name.value !== 'include') { | ||
throw new Error(`Directive ${directive.name.value} not supported.`); | ||
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. I think this should just ignore any directives that are not |
||
} | ||
|
||
//evaluate the "if" argument and skip (i.e. return undefined) if it evaluates to true. | ||
const directiveArguments = directive.arguments; | ||
const directiveName = directive.name.value; | ||
if (directiveArguments.length !== 1) { | ||
throw new Error(`Incorrect number of arguments for the @${directiveName} directive.`); | ||
} | ||
|
||
|
||
const ifArgument = directive.arguments[0]; | ||
if (!ifArgument.name || ifArgument.name.value !== 'if') { | ||
throw new Error(`Invalid argument for the @${directiveName} directive.`); | ||
} | ||
|
||
const ifValue = directive.arguments[0].value; | ||
let evaledValue: Boolean = false; | ||
if (!ifValue || ifValue.kind !== 'BooleanValue') { | ||
// means it has to be a variable value if this is a valid @skip or @include directive | ||
if (ifValue.kind !== 'Variable') { | ||
throw new Error(`Argument for the @${directiveName} directive must be a variable or a bool ean value.`); | ||
} else { | ||
evaledValue = variables[(ifValue as Variable).name.value]; | ||
if (evaledValue === undefined) { | ||
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, don't 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. You could use |
||
throw new Error(`Invalid variable referenced in @${directiveName} directive.`); | ||
} | ||
} | ||
} else { | ||
evaledValue = (ifValue as BooleanValue).value; | ||
} | ||
|
||
if (directiveName === 'skip') { | ||
evaledValue = !evaledValue; | ||
} | ||
|
||
if (!evaledValue) { | ||
res = false; | ||
} | ||
}); | ||
|
||
return res; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,21 +72,29 @@ export function createApolloStore({ | |
reduxRootKey = 'apollo', | ||
initialState, | ||
config = {}, | ||
reportCrashes, | ||
}: { | ||
reduxRootKey?: string, | ||
initialState?: any, | ||
config?: ApolloReducerConfig, | ||
reportCrashes?: boolean, | ||
} = {}): ApolloStore { | ||
const enhancers = []; | ||
|
||
if (reportCrashes === undefined) { | ||
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. I had to add this because otherwise one of the unit tests would print all of this useless stuff every time it ran because it irked the crash reporter. 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. I'm not sure I understand, can you show me later? 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. Use the TypeScript default argument syntax, |
||
reportCrashes = true; | ||
} | ||
|
||
if (typeof window !== 'undefined') { | ||
const anyWindow = window as any; | ||
if (anyWindow.devToolsExtension) { | ||
enhancers.push(anyWindow.devToolsExtension()); | ||
} | ||
} | ||
|
||
enhancers.push(applyMiddleware(crashReporter)); | ||
if (reportCrashes) { | ||
enhancers.push(applyMiddleware(crashReporter)); | ||
} | ||
|
||
return createStore( | ||
combineReducers({ [reduxRootKey]: createApolloReducer(config) }), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,14 @@ import { | |
applyMiddleware, | ||
} from 'redux'; | ||
|
||
import { | ||
createApolloStore, | ||
} from '../src/store'; | ||
|
||
import { | ||
QueryManager, | ||
} from '../src/QueryManager'; | ||
|
||
import { | ||
createNetworkInterface, | ||
HTTPNetworkInterface, | ||
|
@@ -710,6 +718,44 @@ describe('client', () => { | |
}); | ||
}); | ||
|
||
describe('directives', () => { | ||
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. I think we can move these tests to |
||
it('should reject the query promise if skipped data arrives in the result', (done) => { | ||
const query = gql` | ||
query { | ||
fortuneCookie @skip(if: true) | ||
otherThing | ||
}`; | ||
const result = { | ||
fortuneCookie: 'you will go far', | ||
otherThing: 'false', | ||
}; | ||
const networkInterface = mockNetworkInterface( | ||
{ | ||
request: { query }, | ||
result: { data: result }, | ||
} | ||
); | ||
const client = new ApolloClient({ | ||
networkInterface, | ||
}); | ||
// we need this so it doesn't print out a bunch of stuff we don't need | ||
// when we're trying to test an exception. | ||
client.store = createApolloStore({ reportCrashes: false }); | ||
client.queryManager = new QueryManager({ | ||
networkInterface, | ||
store: client.store, | ||
reduxRootKey: 'apollo', | ||
}); | ||
|
||
client.query({ query }).then(() => { | ||
// do nothing | ||
}).catch((error) => { | ||
assert.include(error.message, 'Found extra field'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
|
||
it('should send operationName along with the query to the server', (done) => { | ||
const query = gql` | ||
query myQueryName { | ||
|
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.
Does this import all of graphql now, because the exact path isn't specified?
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.
These are just the types, it doesn't actually import anything afaik