1
- import { ArgumentNode , ASTNode , FieldNode , parseType , TypeNode , VariableNode } from 'graphql' ;
1
+ import {
2
+ ArgumentNode ,
3
+ ASTNode ,
4
+ FieldNode ,
5
+ isValueNode ,
6
+ Kind ,
7
+ parseType ,
8
+ TypeNode ,
9
+ ValueNode ,
10
+ VariableNode
11
+ } from 'graphql' ;
12
+ import Maybe from 'graphql/tsutils/Maybe' ;
2
13
import { NodeAndVarDefs , nodesMatch } from '../ast' ;
3
14
import { identifyFunc } from '../utils' ;
4
15
import Rewriter , { RewriterOpts , Variables } from './Rewriter' ;
@@ -7,7 +18,17 @@ interface FieldArgTypeRewriterOpts extends RewriterOpts {
7
18
argName : string ;
8
19
oldType : string ;
9
20
newType : string ;
10
- coerceVariable ?: ( variable : any ) => any ;
21
+ coerceVariable ?: ( variable : any , context : { variables : Variables ; args : ArgumentNode [ ] } ) => any ;
22
+ /**
23
+ * EXPERIMENTAL:
24
+ * This allows to coerce value of argument when their value is not stored in a variable
25
+ * but comes in the query node itself.
26
+ * NOTE: At the moment, the user has to return the ast value node herself.
27
+ */
28
+ coerceArgumentValue ?: (
29
+ variable : any ,
30
+ context : { variables : Variables ; args : ArgumentNode [ ] }
31
+ ) => Maybe < ValueNode > ;
11
32
}
12
33
13
34
/**
@@ -18,14 +39,27 @@ class FieldArgTypeRewriter extends Rewriter {
18
39
protected argName : string ;
19
40
protected oldTypeNode : TypeNode ;
20
41
protected newTypeNode : TypeNode ;
21
- protected coerceVariable : ( variable : any ) => any ;
42
+ // Passes context with rest of arguments and variables.
43
+ // Quite useful for variable coercion that depends on other arguments/variables
44
+ // (e.g., [offset, limit] to [pageSize, pageNumber] coercion)
45
+ protected coerceVariable : (
46
+ variable : any ,
47
+ context : { variables : Variables ; args : ArgumentNode [ ] }
48
+ ) => any ;
49
+ // (Experimental): Used to coerce arguments whose value
50
+ // does not come in a variable.
51
+ protected coerceArgumentValue : (
52
+ variable : any ,
53
+ context : { variables : Variables ; args : ArgumentNode [ ] }
54
+ ) => Maybe < ValueNode > ;
22
55
23
56
constructor ( options : FieldArgTypeRewriterOpts ) {
24
57
super ( options ) ;
25
58
this . argName = options . argName ;
26
59
this . oldTypeNode = parseType ( options . oldType ) ;
27
60
this . newTypeNode = parseType ( options . newType ) ;
28
61
this . coerceVariable = options . coerceVariable || identifyFunc ;
62
+ this . coerceArgumentValue = options . coerceArgumentValue || identifyFunc ;
29
63
}
30
64
31
65
public matches ( nodeAndVars : NodeAndVarDefs , parents : ASTNode [ ] ) {
@@ -34,39 +68,94 @@ class FieldArgTypeRewriter extends Rewriter {
34
68
const { variableDefinitions } = nodeAndVars ;
35
69
// is this a field with the correct fieldName and arguments?
36
70
if ( node . kind !== 'Field' ) return false ;
37
- if ( node . name . value !== this . fieldName || ! node . arguments ) return false ;
71
+
72
+ // does this field contain arguments?
73
+ if ( ! node . arguments ) return false ;
74
+
38
75
// is there an argument with the correct name and type in a variable?
39
76
const matchingArgument = node . arguments . find ( arg => arg . name . value === this . argName ) ;
40
- if ( ! matchingArgument || matchingArgument . value . kind !== 'Variable' ) return false ;
41
- const varRef = matchingArgument . value . name . value ;
42
77
43
- // does the referenced variable have the correct type?
44
- for ( const varDefinition of variableDefinitions ) {
45
- if ( varDefinition . variable . name . value === varRef ) {
46
- return nodesMatch ( this . oldTypeNode , varDefinition . type ) ;
78
+ if ( ! matchingArgument ) return false ;
79
+
80
+ // argument value is stored in a variable
81
+ if ( matchingArgument . value . kind === 'Variable' ) {
82
+ const varRef = matchingArgument . value . name . value ;
83
+ // does the referenced variable have the correct type?
84
+ for ( const varDefinition of variableDefinitions ) {
85
+ if ( varDefinition . variable . name . value === varRef ) {
86
+ return nodesMatch ( this . oldTypeNode , varDefinition . type ) ;
87
+ }
47
88
}
48
89
}
90
+ // argument value comes in query doc.
91
+ else {
92
+ const argValueNode = matchingArgument . value ;
93
+ return isValueNode ( argValueNode ) ;
94
+ // Would be ideal to do a nodesMatch in here, however argument value nodes
95
+ // have different format for their values than when passed as variables.
96
+ // For instance, are parsed with Kinds as "graphql.Kind" (e.g., INT="IntValue") and not "graphql.TokenKinds" (e.g., INT="Int")
97
+ // So they might not match correctly. Also they dont contain additional parsed syntax
98
+ // as the non-optional symbol "!". So just return true if the argument.value is a ValueNode.
99
+ //
100
+ // return nodesMatch(this.oldTypeNode, parseType(argRef.kind));
101
+ }
102
+
49
103
return false ;
50
104
}
51
105
52
- public rewriteQuery ( { node, variableDefinitions } : NodeAndVarDefs ) {
53
- const varRefName = this . extractMatchingVarRefName ( node as FieldNode ) ;
54
- const newVarDefs = variableDefinitions . map ( varDef => {
55
- if ( varDef . variable . name . value !== varRefName ) return varDef ;
56
- return { ...varDef , type : this . newTypeNode } ;
57
- } ) ;
58
- return { node, variableDefinitions : newVarDefs } ;
106
+ public rewriteQuery (
107
+ { node : astNode , variableDefinitions } : NodeAndVarDefs ,
108
+ variables : Variables
109
+ ) {
110
+ const node = astNode as FieldNode ;
111
+ const varRefName = this . extractMatchingVarRefName ( node ) ;
112
+ // If argument value is stored in a variable
113
+ if ( varRefName ) {
114
+ const newVarDefs = variableDefinitions . map ( varDef => {
115
+ if ( varDef . variable . name . value !== varRefName ) return varDef ;
116
+ return { ...varDef , type : this . newTypeNode } ;
117
+ } ) ;
118
+ return { node, variableDefinitions : newVarDefs } ;
119
+ }
120
+ // If argument value is not stored in a variable but in the query node.
121
+ const matchingArgument = ( node . arguments || [ ] ) . find ( arg => arg . name . value === this . argName ) ;
122
+ if ( node . arguments && matchingArgument ) {
123
+ const args = [ ...node . arguments ] ;
124
+ const newValue = this . coerceArgumentValue ( matchingArgument . value , { variables, args } ) ;
125
+ /**
126
+ * TODO: If somewhow we can get the schema here, we could make the coerceArgumentValue
127
+ * even easier, as we would be able to construct the ast node for the argument value.
128
+ * as of now, the user has to take care of correctly constructing the argument value ast node herself.
129
+ *
130
+ * const schema = makeExecutableSchema({typeDefs})
131
+ * const myCustomType = schema.getType("MY_CUSTOM_TYPE_NAME")
132
+ * const newArgValue = astFromValue(newValue, myCustomType)
133
+ * Object.assign(matchingArgument, { value: newArgValue })
134
+ */
135
+ if ( newValue ) Object . assign ( matchingArgument , { value : newValue } ) ;
136
+ }
137
+ return { node, variableDefinitions } ;
59
138
}
60
139
61
- public rewriteVariables ( { node } : NodeAndVarDefs , variables : Variables ) {
140
+ public rewriteVariables ( { node : astNode } : NodeAndVarDefs , variables : Variables ) {
141
+ const node = astNode as FieldNode ;
62
142
if ( ! variables ) return variables ;
63
- const varRefName = this . extractMatchingVarRefName ( node as FieldNode ) ;
64
- return { ...variables , [ varRefName ] : this . coerceVariable ( variables [ varRefName ] ) } ;
143
+ const varRefName = this . extractMatchingVarRefName ( node ) ;
144
+ const args = [ ...( node . arguments ? node . arguments : [ ] ) ] ;
145
+ return {
146
+ ...variables ,
147
+ ...( varRefName
148
+ ? { [ varRefName ] : this . coerceVariable ( variables [ varRefName ] , { variables, args } ) }
149
+ : { } )
150
+ } ;
65
151
}
66
152
67
153
private extractMatchingVarRefName ( node : FieldNode ) {
68
- const matchingArgument = ( node . arguments || [ ] ) . find ( arg => arg . name . value === this . argName ) ;
69
- return ( ( matchingArgument as ArgumentNode ) . value as VariableNode ) . name . value ;
154
+ const matchingArgument = ( node . arguments || [ ] ) . find (
155
+ arg => arg . name . value === this . argName
156
+ ) as ArgumentNode ;
157
+ const variableNode = matchingArgument . value as VariableNode ;
158
+ return variableNode . kind === Kind . VARIABLE && variableNode . name . value ;
70
159
}
71
160
}
72
161
0 commit comments