-
Notifications
You must be signed in to change notification settings - Fork 3
/
graphql-0.2.0.tm
490 lines (429 loc) · 12.1 KB
/
graphql-0.2.0.tm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
namespace eval graphql {}
namespace eval ::graphql::parse {}
namespace eval ::graphql::regexp {
variable graphql_re {(?xi) # this is the _expanded_ syntax
^\s*
(?: # Capture the first value (query|mutation) -> $type
# if this value is not defined, query is expected.
([^\s\(\{]*)
(?:(?!\()\s*)?
)?
(?: # A query/mutation may optionally define a name
(?!\()
([^\s\(\{]*)
)?
(?:\(([^\)]*)\))? # Capture the values within the variables scope
\s*
(?:{\s*(.*)\s*})
$
}
# The start of the parsing, we capture the next value in the body
# which starts the process of capturing downwards.
variable graphql_body_re {(?xi)
^\s*
(?!\})
([^\s\(\{:]*) # capture the name of the query
(?: # optionally may define a name and fn mapping
(?=\s*:)
\s*: # if a colon, then we need the fn name next
\s*
(?:
((?:
(?=\[) # WARNING: Breaking Schema Syntax Here for Tcl specific sugar
\[[^\]]*\] # allow passing tcl calls directly as a sugaring to GraphQL
# when providing a name (name: [myProc arg arg])
) |
(?:
[^\s\(\{]* # capture the name of the fn
))
)
)?
\s*
(?:\(([^\)]*)\))? # optionally capture var definitions
\s*
(.*) # grab the rest of the request
$
}
variable graphql_next_query_re {(?xi)
^\s*(?:\{\s*)?
(?!\})
([^\s\(\{:]*) # capture the name of the query
(?: # optionally may define a name and fn mapping
(?=\s*:)
\s*: # if a colon, then we need the fn name next
\s*
(?:
((?:
(?=\[) # WARNING: Breaking Schema Syntax Here for Tcl specific sugar
\[[^\]]*\] # allow passing tcl calls directly as a sugaring to GraphQL
) |
(?:
[^\s\(\{]* # capture the name of the fn
))
)
)?
(?:
(?=\s*\() # check if we have arg declarations
\s*
\(
([^\)]*) # grab the arg values
\)
)?
(?: # directives @include(if: $boolean) / @skip(if: $boolean)
(?=\s*@)
\s*(@[^\(]*\([^\)]*\)) # capture the full directive to be parsed
# we only capture the full directive and
# run another query to get the fragments
# if needed.
)?
(?:
(?=\s*\{) # this is a object type, capture the rest so we know
# to continue parsing
\s*\{
(.*)
$
)?
\s* # this will only have a value if we are done parsing
(.*) # this value, otherwise its sibling will.
}
variable graphql_directive_re {(?xi)
^@
([^\s\(]*) # the directive type - currently "include" or "skip"
\s*\(if:\s*
([^\s\)]*) # capture the variable to check against
\s*\)
$
}
}
proc ::graphql::parse data {
set parsed [dict create]
set definitions {}
set type {}
if {[dict exists $data variables]} {
dict set parsed variables [dict get $data variables]
}
if {[dict exists $data query query]} {
set query [string trim [dict get $data query query]]
}
regexp -- $regexp::graphql_re $query \
-> type name definitions body
dict set parsed type $type
dict set parsed name $name
set body [string trim $body]
if {$definitions ne {}} {
::graphql::parse::definitions $definitions
}
::graphql::parse::body $body
return $parsed
}
proc ::graphql::parse::definitions definitions {
upvar 1 parsed parsed
foreach {var type} $definitions {
if {$var eq "="} {
set default [string trim $type " \"'\{\}"]
# we are defining a default value to the previous variable
if {![dict exists $parsed variables $lastParsedVar]} {
dict set parsed variables $lastParsedVar $default
}
dict set parsed definitions $lastParsedVar default $default
continue
}
set var [string trimright $var :]
set var [string trimleft $var \$]
if {[string match "*!" $type]} {
set type [string trimright $type !]
set required 1
if {![dict exists $parsed variables $var]} {
tailcall return \
-code error \
-errorCode [list GRAPHQL PARSE VAR_NOT_DEFINED] \
" variable $var is required but it was not provided within the request"
}
} else {
set required 0
}
if {[string index $type 0] eq "\["} {
set isArray 1
set type [string range $type 1 end-1]
} else {
set isArray 0
}
if {[dict exists $parsed variables $var]} {
set varValue [dict get $parsed variables $var]
} else {
unset -nocomplain varValue
}
set type [string tolower $type]
switch -- $type {
float {
set type double
set checkType 1
}
boolean {
set checkType 1
}
int {
set type integer
set checkType 1
}
default {
set checkType 0
}
}
if {$checkType && [info exists varValue]} {
if {$isArray} {
set i 0
foreach elval $varValue {
if {![string is $type -strict $elval]} {
tailcall return \
-code error \
-errorCode [list GRAPHQL PARSE VAR_INVALID_TYPE IN_ARRAY] \
" variable $var element $i should be ${type} but received: \"$elval\" while checking \"Array<${varValue}>\""
}
incr i
}
} elseif {![string is $type -strict $varValue]} {
tailcall return \
-code error \
-errorCode [list GRAPHQL PARSE VAR_INVALID_TYPE IN_ARRAY] \
" variable $var should be type \"${type}\" but received: \"$varValue\""
}
}
set lastParsedVar $var
dict set parsed definitions $var [dict create \
type [string tolower $type] \
required $required
]
}
}
proc ::graphql::parse::arg arg {
upvar 1 parsed parsed
if {[dict exists $parsed variables]} {
set variables [dict get $parsed variables]
}
if {[string index $arg 0] eq "\$"} {
set name [string range $arg 1 end]
if {[dict exists $variables $name]} {
set arg [dict get $variables $name]
} else {
# our parsing should have already given an error if it detected
# this value shoudl be defined - we will simply set it to {}
set arg {}
#return -code error " variable $name not found for arg $arg"
}
}
return $arg
}
proc ::graphql::parse::fnargs fnargs {
upvar 1 parsed parsed
set data [dict create]
# set argName {}
# set argValue {}
foreach arg $fnargs {
set arg [string trim $arg]
if {$arg eq ":"} {
continue
}
if {[info exists argValue]} {
# Once defined, we can set the value and unset our vars
dict set data [arg $argName] [arg $argValue]
unset argName
unset argValue
}
if {![info exists argName]} {
set colonIdx [string first : $arg]
if {$colonIdx != -1} {
if {[string index $arg end] eq ":"} {
set argName [string trimright $arg :]
} else {
lassign [split $arg :] argName argValue
}
} else {
# this is probably not right?
set argName $arg
}
} else {
set argValue $arg
}
}
if {[info exists argName] && [info exists argValue]} {
dict set data [arg $argName] [arg $argValue]
}
return $data
}
proc ::graphql::parse::directive directive {
upvar 1 parsed parsed
if {[dict exists $parsed variables]} {
set variables [dict get $parsed variables]
} else {
set variables [dict create]
}
regexp -- $::graphql::regexp::graphql_directive_re $directive \
-> type var
if {[string index $var 0] eq "\$"} {
set name [string range $var 1 end]
if {[dict exists $variables $name]} {
set val [dict get $variables $name]
}
} else {
set val $var
}
switch -nocase -- $type {
include {
if {![info exists val] || ![string is true -strict $val]} {
return 0
}
}
skip {
if {[info exists val] && [string is true -strict $val]} {
return 0
}
}
default {
return tailcall \
-code error \
-errorCode [list GRAPHQL BAD_DIRECTIVE] \
" provided a directive of type $type ($directive). This is not supported by the GraphQL Syntax."
}
}
return 1
}
proc ::graphql::parse::body remaining {
upvar 1 parsed parsed
# set lvl 1
while {$remaining ne {}} {
set props [list]
regexp -- $::graphql::regexp::graphql_body_re $remaining \
-> name fn fnargs remaining
if {![info exists name] || $name eq {}} {
break
}
if {[string index $name 0] eq "\$"} {
if {[dict exists $parsed variables [string range $name 1 end]]} {
set name [dict get $parsed variables [string range $name 1 end]]
}
}
if {$fn eq {}} {
set fn $name
}
if {$fnargs ne {}} {
set fnargs [::graphql::parse::fnargs $fnargs]
}
set remaining [nextType $remaining]
dict lappend parsed requests $name [dict create \
name $name \
fn $fn \
args $fnargs \
props $props
]
set remaining [string trimleft $remaining "\}\n "]
}
}
proc ::graphql::parse::nextType remaining {
upvar 1 props pprops
upvar 1 parsed parsed
# upvar 1 lvl plvl
# set lvl [expr {$plvl + 1}]
while {[string index $remaining 0] ne "\}" && $remaining ne {}} {
unset -nocomplain name
set skip 0
set props [list]
regexp -- $::graphql::regexp::graphql_next_query_re $remaining \
-> name fn fnargs directive schema remaining
if {![info exists name] || $name eq {}} {
break
}
if {[string index $name 0] eq "\$"} {
if {[dict exists $parsed variables [string range $name 1 end]]} {
set name [dict get $parsed variables [string range $name 1 end]]
}
}
if {$directive ne {}} {
# directive will tell us whether or not we should be
# including the value.
if {![::graphql::parse::directive $directive]} {
set skip 1
}
}
set prop [dict create name $name]
if {[info exists fnargs] && $fnargs ne {}} {
set fnargs [::graphql::parse::fnargs $fnargs]
dict set prop args $fnargs
}
if {[info exists schema]} {
set schema [string trim $schema]
if {$schema ne {}} {
if {$fn eq {}} {
set fn $name
}
dict set prop fn $fn
set schema [nextType $schema]
set schema [string trim $schema]
# remove the trailing curly bracket
if {[string index $schema 0] eq "\}"} {
set remaining [string range $schema 1 end]
} else {
set remaining $schema
}
}
}
if {[string is false $skip]} {
if {[llength $props] > 0} {
dict set prop props $props
}
lappend pprops $prop
}
set remaining [string trim $remaining]
}
# At this point, $schema will have content if we need to continue
# parsing this type, otherwise it will be within remaining
return $remaining
}
# proc printProp prop {
# upvar 1 lvl plvl
# set lvl [expr { $plvl + 1 }]
# set prefix [string repeat " " $lvl]
# puts "$prefix -- PROP -- [dict get $prop name]"
# if {[dict exists $prop args]} {
# puts "$prefix Args: [dict get $prop args]"
# }
# if {[dict exists $prop fnargs]} {
# puts "$prefix FN Args: [dict get $prop fnargs]"
# }
# if {[dict exists $prop props]} {
# puts "$prefix - Total Props [llength [dict get $prop props]]"
# foreach cprop [dict get $prop props] {
# printProp $cprop
# }
# }
# }
#
# proc print {} {
# set lvl 0
# foreach {k v} $::result {
# switch -- $k {
# requests {
# foreach {query schema} $v {
# puts "
# --- QUERY $query ---
# "
# printProp $schema
# }
# }
# default {
# puts "-$k -> $v"
# }
# }
# }
# puts "
# Time to Parse: [expr {$::stop - $::start}] microseconds
# "
# }
#
# proc parse {} {
# set data [json get $::PACKET]
# set ::start [clock microseconds]
# set ::result [::graphql::parse $data]
# set ::stop [clock microseconds]
# print
# }