agoo 2.11.7 → 2.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/bin/agoo_stubs +166 -0
- data/ext/agoo/bind.c +7 -5
- data/ext/agoo/con.c +1 -1
- data/ext/agoo/gqleval.c +87 -23
- data/ext/agoo/gqleval.h +6 -0
- data/ext/agoo/gqlintro.c +175 -103
- data/ext/agoo/gqljson.c +3 -0
- data/ext/agoo/gqlvalue.c +5 -5
- data/ext/agoo/graphql.c +35 -24
- data/ext/agoo/graphql.h +11 -15
- data/ext/agoo/req.c +9 -10
- data/ext/agoo/rgraphql.c +248 -12
- data/ext/agoo/rserver.c +52 -5
- data/ext/agoo/rserver.h +8 -0
- data/ext/agoo/sdl.c +23 -26
- data/ext/agoo/sdl.h +1 -1
- data/lib/agoo.rb +1 -0
- data/lib/agoo/graphql.rb +9 -0
- data/lib/agoo/graphql/arg.rb +13 -0
- data/lib/agoo/graphql/field.rb +14 -0
- data/lib/agoo/graphql/type.rb +12 -0
- data/lib/agoo/version.rb +1 -1
- data/test/graphql_test.rb +22 -18
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df42da153e97e018d4984fa75be78ac86a1c8b97aed6b48d62ec7fe5047d7099
|
4
|
+
data.tar.gz: e83a55eed4781bbb0ab08df41203509df6ec45d06817b87c9d61d25bca8a294a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac6e8d86a9b545d93c9ee033950422292b4ab2f740ba692f2c24940e928c42466b895dfae6ce5263040f2b53613a6c1241f5dfe5c0be21961f1ee885970ec220
|
7
|
+
data.tar.gz: 9dd8414c88948198bd1a22d5aac236ef8d5bf4251020b7fcfc4b94837d8a48433aa6774ba9e13df1dc36dc1e29f1697dfcc2c21e8ef1a9dc6897d223b72eb946
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,54 @@
|
|
2
2
|
|
3
3
|
All changes to the Agoo gem are documented here. Releases follow semantic versioning.
|
4
4
|
|
5
|
+
## [2.13.0] - 2020-07-05
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- Agoo::Server.use() added. It works similar to the Rack use() function.
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
|
13
|
+
- Header checks are now case insensitive to comply with RFC 7230.
|
14
|
+
|
15
|
+
## [2.12.3] - 2020-03-28
|
16
|
+
|
17
|
+
rb_rescue2 termination
|
18
|
+
|
19
|
+
### Fixed
|
20
|
+
|
21
|
+
- rb_rescue2 must be terminated by a (VALUE)0 and not simply 0.
|
22
|
+
|
23
|
+
## [2.12.2] - 2020-03-22
|
24
|
+
|
25
|
+
Stub generator
|
26
|
+
|
27
|
+
### Added
|
28
|
+
|
29
|
+
- GraphQL stub generator to generate Ruby class file corresponding to GraphQL schema type. The generator is `bin/agoo_stubs`.
|
30
|
+
|
31
|
+
## [2.12.1] - 2020-03-17
|
32
|
+
|
33
|
+
Check SO_REUSEPORT
|
34
|
+
|
35
|
+
### Fixed
|
36
|
+
|
37
|
+
- Verifiy SO_REUSEPORT is define for older OS versions.
|
38
|
+
|
39
|
+
## [2.12.0] - 2020-01-19
|
40
|
+
|
41
|
+
Request GraphQL access and GraphiQL support
|
42
|
+
|
43
|
+
### Added
|
44
|
+
|
45
|
+
- Optional addition of request information is now available in GraphQL resolve functions.
|
46
|
+
|
47
|
+
- Headers for GraphQL responses can now be set.
|
48
|
+
|
49
|
+
- Fixed incorrect values in introspection responses.
|
50
|
+
|
51
|
+
- Fixed JSON parser bug with null, true, and false.
|
52
|
+
|
5
53
|
## [2.11.7] - 2020-01-02
|
6
54
|
|
7
55
|
Ruby 2.7.0 cleanup
|
data/bin/agoo_stubs
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
while (index = ARGV.index('-I'))
|
4
|
+
_, path = ARGV.slice!(index, 2)
|
5
|
+
$LOAD_PATH << path
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
require 'agoo'
|
11
|
+
|
12
|
+
usage = %{
|
13
|
+
Usage: agoo_stubs [options] <graphql_schema>
|
14
|
+
|
15
|
+
version #{Agoo::VERSION}
|
16
|
+
|
17
|
+
Generates Ruby files that match the types in the GraphQL schema file specified.
|
18
|
+
|
19
|
+
Example:
|
20
|
+
|
21
|
+
agoo_stubs --module Data --dir lib schema.graphql
|
22
|
+
|
23
|
+
}
|
24
|
+
|
25
|
+
@verbose = false
|
26
|
+
@single = nil
|
27
|
+
@dir = nil
|
28
|
+
@mod = nil
|
29
|
+
|
30
|
+
@opts = OptionParser.new(usage)
|
31
|
+
@opts.on('-h', '--help', 'show this display') { puts @opts.help; Process.exit!(0) }
|
32
|
+
@opts.on('-v', '--verbose', 'verbose output') { @verbose = true }
|
33
|
+
@opts.on('-s', '--single PATH', String, 'path to file for all stubs') { |s| @single = s }
|
34
|
+
@opts.on('-d', '--dir PATH', String, 'directory to write files into') { |d| @dir = d }
|
35
|
+
@opts.on('-m', '--module STRING', String, 'module for the stub classes') { |m| @mod = m }
|
36
|
+
|
37
|
+
@files = @opts.parse(ARGV)
|
38
|
+
|
39
|
+
if @files.empty?
|
40
|
+
puts "A schema file must be specified."
|
41
|
+
puts @opts.help
|
42
|
+
Process.exit!(0)
|
43
|
+
elsif 1 < @files.size
|
44
|
+
puts "Only one schema file can be specified."
|
45
|
+
puts @opts.help
|
46
|
+
Process.exit!(0)
|
47
|
+
end
|
48
|
+
|
49
|
+
Agoo::GraphQL.schema(nil) {
|
50
|
+
Agoo::GraphQL.load_file(@files[0])
|
51
|
+
}
|
52
|
+
|
53
|
+
def write_type_stub(f, t, depth)
|
54
|
+
indent = ' ' * depth
|
55
|
+
f.puts "#{indent}# #{t.description}" unless t.description.nil?
|
56
|
+
f.puts "#{indent}class #{t.name}"
|
57
|
+
t.fields.each { |field|
|
58
|
+
next unless field.args.nil?
|
59
|
+
f.puts "#{indent} # #{field.description}" unless field.description.nil?
|
60
|
+
f.puts "#{indent} attr_accessor :#{field.name}"
|
61
|
+
}
|
62
|
+
f.puts
|
63
|
+
f.puts "#{indent} def initialize"
|
64
|
+
f.puts "#{indent} end"
|
65
|
+
t.fields.each { |field|
|
66
|
+
next if field.args.nil?
|
67
|
+
f.puts
|
68
|
+
f.puts "#{indent} # #{field.description}" unless field.description.nil?
|
69
|
+
f.puts "#{indent} # args: #{field.args.map { |a| a.name }.join(', ')}"
|
70
|
+
f.puts "#{indent} def #{field.name}(args={})"
|
71
|
+
f.puts "#{indent} end"
|
72
|
+
}
|
73
|
+
f.puts "#{indent}end"
|
74
|
+
end
|
75
|
+
|
76
|
+
def calc_filepath(t)
|
77
|
+
path = @dir
|
78
|
+
path += '/' + @mod.split('::').map { |m| m.downcase }.join('/') unless @mod.nil?
|
79
|
+
path + '/' + t.name.downcase + '.rb'
|
80
|
+
end
|
81
|
+
|
82
|
+
def assure_parent_dir(path)
|
83
|
+
dirs = path.split('/')[0..-2]
|
84
|
+
dirs.each_index { |i|
|
85
|
+
path = dirs[0..i].join('/')
|
86
|
+
Dir.mkdir(path) unless Dir.exist?(path)
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_type_stub_file(t)
|
91
|
+
path = calc_filepath(t)
|
92
|
+
assure_parent_dir(path)
|
93
|
+
File.open(path, 'w') { |f|
|
94
|
+
f.puts "# #{t.name} class for schema file #{@files[0]}"
|
95
|
+
f.puts
|
96
|
+
depth = 0
|
97
|
+
unless @mod.nil?
|
98
|
+
@mod.split('::').each { |m|
|
99
|
+
f.puts("#{' ' * depth}module #{m}")
|
100
|
+
depth += 1
|
101
|
+
}
|
102
|
+
f.puts
|
103
|
+
end
|
104
|
+
write_type_stub(f, t, depth)
|
105
|
+
f.puts
|
106
|
+
unless @mod.nil?
|
107
|
+
@mod.split('::').size.times {
|
108
|
+
depth -= 1
|
109
|
+
f.puts("#{' ' * depth}end")
|
110
|
+
}
|
111
|
+
end
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
@out = nil
|
116
|
+
|
117
|
+
if @dir.nil?
|
118
|
+
if @single.nil?
|
119
|
+
@out = STDOUT
|
120
|
+
else @single
|
121
|
+
@out = File.new(@single, 'w')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
unless @out.nil?
|
126
|
+
@out.puts "# Classes for schema file #{@files[0]}"
|
127
|
+
@out.puts
|
128
|
+
depth = 0
|
129
|
+
unless @mod.nil?
|
130
|
+
@mod.split('::').each { |m|
|
131
|
+
@out.puts("#{' ' * depth}module #{m}")
|
132
|
+
depth += 1
|
133
|
+
}
|
134
|
+
@out.puts
|
135
|
+
end
|
136
|
+
Agoo::GraphQL.sdl_types.sort { |a,b| a.name <=> b.name }.each { |t|
|
137
|
+
write_type_stub(@out, t, depth)
|
138
|
+
@out.puts
|
139
|
+
}
|
140
|
+
unless @mod.nil?
|
141
|
+
@mod.split('::').size.times {
|
142
|
+
depth -= 1
|
143
|
+
@out.puts("#{' ' * depth}end")
|
144
|
+
}
|
145
|
+
end
|
146
|
+
@out.close unless STDOUT == @out
|
147
|
+
else
|
148
|
+
Agoo::GraphQL.sdl_types.each { |t|
|
149
|
+
write_type_stub_file(t)
|
150
|
+
}
|
151
|
+
unless @mod.nil?
|
152
|
+
path = @dir
|
153
|
+
path += '/' + @mod.split('::').map { |m| m.downcase }.join('/')
|
154
|
+
assure_parent_dir(path)
|
155
|
+
m = path.split('/')[-1]
|
156
|
+
path += '.rb'
|
157
|
+
|
158
|
+
File.open(path, 'w') { |f|
|
159
|
+
f.puts "# Classes for schema file #{@files[0]}"
|
160
|
+
f.puts
|
161
|
+
Agoo::GraphQL.sdl_types.sort { |a,b| a.name <=> b.name }.each { |t|
|
162
|
+
f.puts "require_relative '#{m}/#{t.name.downcase}'"
|
163
|
+
}
|
164
|
+
}
|
165
|
+
end
|
166
|
+
end
|
data/ext/agoo/bind.c
CHANGED
@@ -223,25 +223,25 @@ url_ssl(agooErr err, const char *url) {
|
|
223
223
|
|
224
224
|
agooBind
|
225
225
|
agoo_bind_url(agooErr err, const char *url) {
|
226
|
-
if (0 ==
|
226
|
+
if (0 == strncasecmp("tcp://", url, 6)) {
|
227
227
|
if ('[' == url[6]) {
|
228
228
|
return url_tcp6(err, url + 6, "tcp");
|
229
229
|
}
|
230
230
|
return url_tcp(err, url + 6, "tcp");
|
231
231
|
}
|
232
|
-
if (0 ==
|
232
|
+
if (0 == strncasecmp("http://", url, 7)) {
|
233
233
|
if ('[' == url[7]) {
|
234
234
|
return url_tcp6(err, url + 7, "http");
|
235
235
|
}
|
236
236
|
return url_tcp(err, url + 7, "http");
|
237
237
|
}
|
238
|
-
if (0 ==
|
238
|
+
if (0 == strncasecmp("unix://", url, 7)) {
|
239
239
|
return url_named(err, url + 7);
|
240
240
|
}
|
241
|
-
if (0 ==
|
241
|
+
if (0 == strncasecmp("https://", url, 8)) {
|
242
242
|
return url_ssl(err, url + 8);
|
243
243
|
}
|
244
|
-
if (0 ==
|
244
|
+
if (0 == strncasecmp("ssl://", url, 6)) {
|
245
245
|
return url_ssl(err, url + 6);
|
246
246
|
}
|
247
247
|
// All others assume http
|
@@ -286,7 +286,9 @@ usual_listen(agooErr err, agooBind b) {
|
|
286
286
|
#ifdef OSX_OS
|
287
287
|
setsockopt(b->fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval));
|
288
288
|
#endif
|
289
|
+
#ifdef SO_REUSEPORT
|
289
290
|
setsockopt(b->fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
|
291
|
+
#endif
|
290
292
|
setsockopt(b->fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
|
291
293
|
if (AF_INET6 == b->family) {
|
292
294
|
struct sockaddr_in6 addr;
|
data/ext/agoo/con.c
CHANGED
@@ -173,7 +173,7 @@ agoo_con_header_value(const char *header, int hlen, const char *key, int *vlen)
|
|
173
173
|
int klen = (int)strlen(key);
|
174
174
|
|
175
175
|
while (h < hend) {
|
176
|
-
if (0 ==
|
176
|
+
if (0 == strncasecmp(key, h, klen) && ':' == h[klen]) {
|
177
177
|
h += klen + 1;
|
178
178
|
for (; ' ' == *h; h++) {
|
179
179
|
}
|
data/ext/agoo/gqleval.c
CHANGED
@@ -36,8 +36,9 @@ static const char query_str[] = "query";
|
|
36
36
|
static const char subscription_str[] = "subscription";
|
37
37
|
static const char variables_str[] = "variables";
|
38
38
|
|
39
|
-
|
40
39
|
gqlValue (*gql_doc_eval_func)(agooErr err, gqlDoc doc) = NULL;
|
40
|
+
agooText (*gql_build_headers)(agooErr err, agooReq req, agooText headers) = NULL;
|
41
|
+
agooText gql_headers = NULL;
|
41
42
|
|
42
43
|
static void
|
43
44
|
err_resp(agooRes res, agooErr err, int status) {
|
@@ -93,25 +94,74 @@ value_resp(agooReq req, gqlValue result, int status, int indent) {
|
|
93
94
|
break;
|
94
95
|
default:
|
95
96
|
agoo_log_cat(&agoo_error_cat, "Did not expect an HTTP status of %d.", status);
|
96
|
-
|
97
|
+
agoo_err_set(&err, AGOO_ERR_EVAL, "Did not expect an HTTP status of %d.", status);
|
98
|
+
goto FAILED;
|
97
99
|
}
|
98
100
|
agoo_res_message_push(res, text);
|
99
101
|
return;
|
100
102
|
}
|
101
103
|
if (AGOO_ERR_OK != gql_object_set(&err, msg, "data", result)) {
|
102
|
-
|
103
|
-
gql_value_destroy(result);
|
104
|
-
return;
|
104
|
+
goto FAILED;
|
105
105
|
}
|
106
106
|
text = gql_value_json(text, msg, indent, 0);
|
107
107
|
gql_value_destroy(msg); // also destroys result
|
108
|
+
result = NULL;
|
108
109
|
|
109
110
|
cnt = snprintf(buf, sizeof(buf), "HTTP/1.1 %d %s\r\nContent-Type: application/json\r\nContent-Length: %ld\r\n\r\n",
|
110
111
|
status, agoo_http_code_message(status), text->len);
|
111
|
-
if (NULL
|
112
|
-
|
112
|
+
if (NULL != gql_headers || NULL != gql_build_headers) {
|
113
|
+
agooText headers = agoo_text_allocate(4094);
|
114
|
+
|
115
|
+
headers = agoo_text_append(headers, buf, cnt - 2);
|
116
|
+
if (NULL == headers) {
|
117
|
+
agoo_log_cat(&agoo_error_cat, "Failed to allocate memory for a response.");
|
118
|
+
return;
|
119
|
+
}
|
120
|
+
if (NULL != gql_headers) {
|
121
|
+
headers = agoo_text_append(headers, gql_headers->text, (int)gql_headers->len);
|
122
|
+
} else {
|
123
|
+
headers = gql_build_headers(&err, req, headers);
|
124
|
+
}
|
125
|
+
headers = agoo_text_append(headers, "\r\n", 2);
|
126
|
+
if (NULL == headers) {
|
127
|
+
agoo_log_cat(&agoo_error_cat, "Failed to allocate memory for a response.");
|
128
|
+
return;
|
129
|
+
}
|
130
|
+
if (NULL == (text = agoo_text_prepend(text, headers->text, (int)headers->len))) {
|
131
|
+
agoo_log_cat(&agoo_error_cat, "Failed to allocate memory for a response.");
|
132
|
+
agoo_text_release(headers);
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
agoo_text_release(headers);
|
136
|
+
} else {
|
137
|
+
if (NULL == (text = agoo_text_prepend(text, buf, cnt))) {
|
138
|
+
agoo_log_cat(&agoo_error_cat, "Failed to allocate memory for a response.");
|
139
|
+
return;
|
140
|
+
}
|
113
141
|
}
|
114
142
|
agoo_res_message_push(res, text);
|
143
|
+
|
144
|
+
return;
|
145
|
+
|
146
|
+
FAILED:
|
147
|
+
err_resp(res, &err, 500);
|
148
|
+
if (NULL != result) {
|
149
|
+
gql_value_destroy(result);
|
150
|
+
}
|
151
|
+
return;
|
152
|
+
}
|
153
|
+
|
154
|
+
agooText
|
155
|
+
gql_add_header(agooErr err, agooText headers, const char *key, const char *value) {
|
156
|
+
headers = agoo_text_append(headers, key, -1);
|
157
|
+
headers = agoo_text_append(headers, ": ", 2);
|
158
|
+
headers = agoo_text_append(headers, value, -1);
|
159
|
+
headers = agoo_text_append(headers, "\r\n", 2);
|
160
|
+
|
161
|
+
if (NULL == headers) {
|
162
|
+
AGOO_ERR_MEM(err, "headers");
|
163
|
+
}
|
164
|
+
return headers;
|
115
165
|
}
|
116
166
|
|
117
167
|
gqlValue
|
@@ -416,6 +466,7 @@ gql_eval_get_hook(agooReq req) {
|
|
416
466
|
return;
|
417
467
|
}
|
418
468
|
set_doc_op(doc, op_name, oplen);
|
469
|
+
doc->req = req;
|
419
470
|
|
420
471
|
if (NULL == gql_doc_eval_func) {
|
421
472
|
result = gql_doc_eval(&err, doc);
|
@@ -488,8 +539,6 @@ eval_post(agooErr err, agooReq req) {
|
|
488
539
|
gqlValue result = NULL;
|
489
540
|
gqlValue j = NULL;
|
490
541
|
|
491
|
-
// TBD handle query parameter and concatenate with JSON body variables if present
|
492
|
-
|
493
542
|
op_name = agoo_req_query_value(req, operation_name_str, sizeof(operation_name_str) - 1, &oplen);
|
494
543
|
var_json = agoo_req_query_value(req, variables_str, sizeof(variables_str) - 1, &vlen);
|
495
544
|
|
@@ -502,11 +551,11 @@ eval_post(agooErr err, agooReq req) {
|
|
502
551
|
agoo_err_set(err, AGOO_ERR_TYPE, "required Content-Type not in the HTTP header");
|
503
552
|
return NULL;
|
504
553
|
}
|
505
|
-
if (0 ==
|
554
|
+
if (0 == strncasecmp(graphql_content_type, s, sizeof(graphql_content_type) - 1)) {
|
506
555
|
if (NULL == (doc = sdl_parse_doc(err, req->body.start, req->body.len, vars, GQL_QUERY))) {
|
507
556
|
return NULL;
|
508
557
|
}
|
509
|
-
} else if (0 ==
|
558
|
+
} else if (0 == strncasecmp(json_content_type, s, sizeof(json_content_type) - 1)) {
|
510
559
|
gqlLink m;
|
511
560
|
|
512
561
|
if (NULL != (j = gql_json_parse(err, req->body.start, req->body.len))) {
|
@@ -532,19 +581,24 @@ eval_post(agooErr err, agooReq req) {
|
|
532
581
|
} else if (0 == strcmp("variables", m->key)) {
|
533
582
|
gqlLink link;
|
534
583
|
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
for (link = m->value->members; NULL != link; link = link->next) {
|
540
|
-
gqlVar v = gql_op_var_create(err, link->key, link->value->type, link->value);
|
584
|
+
switch (m->value->type->scalar_kind) {
|
585
|
+
case GQL_SCALAR_OBJECT:
|
586
|
+
for (link = m->value->members; NULL != link; link = link->next) {
|
587
|
+
gqlVar v = gql_op_var_create(err, link->key, link->value->type, link->value);
|
541
588
|
|
542
|
-
|
543
|
-
|
544
|
-
|
589
|
+
link->value = NULL;
|
590
|
+
if (NULL == v) {
|
591
|
+
goto DONE;
|
592
|
+
}
|
593
|
+
v->next = vars;
|
594
|
+
vars = v;
|
545
595
|
}
|
546
|
-
|
547
|
-
|
596
|
+
break;
|
597
|
+
case GQL_SCALAR_NULL:
|
598
|
+
break;
|
599
|
+
default:
|
600
|
+
agoo_err_set(err, AGOO_ERR_EVAL, "expected variables to be an object or null.");
|
601
|
+
goto DONE;
|
548
602
|
}
|
549
603
|
}
|
550
604
|
}
|
@@ -559,6 +613,7 @@ eval_post(agooErr err, agooReq req) {
|
|
559
613
|
return NULL;
|
560
614
|
}
|
561
615
|
set_doc_op(doc, op_name, oplen);
|
616
|
+
doc->req = req;
|
562
617
|
|
563
618
|
if (NULL == gql_doc_eval_func) {
|
564
619
|
result = gql_doc_eval(err, doc);
|
@@ -569,7 +624,9 @@ eval_post(agooErr err, agooReq req) {
|
|
569
624
|
result = NULL;
|
570
625
|
}
|
571
626
|
DONE:
|
572
|
-
|
627
|
+
if (NULL != doc) {
|
628
|
+
gql_doc_destroy(doc);
|
629
|
+
}
|
573
630
|
gql_value_destroy(j);
|
574
631
|
|
575
632
|
return result;
|
@@ -595,6 +652,13 @@ gql_eval_post_hook(agooReq req) {
|
|
595
652
|
}
|
596
653
|
}
|
597
654
|
|
655
|
+
void
|
656
|
+
gql_eval_options_hook(agooReq req) {
|
657
|
+
struct _agooErr err = AGOO_ERR_INIT;
|
658
|
+
|
659
|
+
value_resp(req, gql_object_create(&err), 200, 0);
|
660
|
+
}
|
661
|
+
|
598
662
|
gqlValue
|
599
663
|
gql_get_arg_value(gqlKeyVal args, const char *key) {
|
600
664
|
gqlValue value = NULL;
|