agoo 2.11.7 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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;
|