agoo 2.14.2 → 2.15.1
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 +31 -0
- data/ext/agoo/con.c +2 -0
- data/ext/agoo/gqleval.c +19 -3
- data/ext/agoo/gqlintro.c +2 -2
- data/ext/agoo/request.c +19 -0
- data/ext/agoo/rgraphql.c +24 -2
- data/ext/agoo/rserver.c +24 -10
- data/lib/agoo/version.rb +1 -1
- data/test/graphql_test.rb +32 -1
- data/test/rack_handler_test.rb +18 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74cca7bbc7156c967212d01e349f2d9134af318c8abd59687167fa5580773e54
|
4
|
+
data.tar.gz: cf765b3109277f2caa4fbb2c25d5352990fd03a394a78de82cf81ed4b235b6f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50ef0edcf1dcec15be40de1594ddd9dbbec5749fd9099f140ffd0ce986a50728d3ff442e63f4a2756f0bdcd082618cf37a183b31676c432cddb07f4ec916d48f
|
7
|
+
data.tar.gz: 485447bbd3013e516472a8f6531316eb5d1b4144b3197359425cc838807878756c990145c68ecaa5322a157ce93601a671449291fbece8cae05f0fe35d9c1f56
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,37 @@
|
|
2
2
|
|
3
3
|
All changes to the Agoo gem are documented here. Releases follow semantic versioning.
|
4
4
|
|
5
|
+
## [2.15.1] - 2022-06-18
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- Introspection can now be disabled with the `hide_schema` option to the Agoo server.
|
10
|
+
|
11
|
+
- If an exception raised from a GraphQL callback responds to `code`
|
12
|
+
that code will be used as the HTTP status.
|
13
|
+
|
14
|
+
- Added missing ability to set or add elements of a request argument
|
15
|
+
on GraphQL callback methods.
|
16
|
+
|
17
|
+
## [2.15.0] - 2022-05-20
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- Support added for PATCH.
|
22
|
+
|
23
|
+
- A `:hide_schema` option has been added to show the graphql/schema as
|
24
|
+
not found unless added by with the handle method of the server.
|
25
|
+
|
26
|
+
- Raising an exception that responds to `code` in a graphql resolve
|
27
|
+
function will return that code as the HTTP status code.
|
28
|
+
|
29
|
+
## [2.14.3] - 2022-05-05
|
30
|
+
|
31
|
+
### Fixed
|
32
|
+
- Agoo now reports an error if the developer make the mistake of
|
33
|
+
building a schema that loops back on itself too many times using
|
34
|
+
fragments.
|
35
|
+
|
5
36
|
## [2.14.2] - 2022-02-22
|
6
37
|
|
7
38
|
### Fixed
|
data/ext/agoo/con.c
CHANGED
@@ -327,6 +327,8 @@ con_header_read(agooCon c, size_t *mlenp) {
|
|
327
327
|
method = AGOO_PUT;
|
328
328
|
} else if (4 == b - c->buf && 0 == strncmp("POST", c->buf, 4)) {
|
329
329
|
method = AGOO_POST;
|
330
|
+
} else if (5 == b - c->buf && 0 == strncmp("PATCH", c->buf, 5)) {
|
331
|
+
method = AGOO_PATCH;
|
330
332
|
} else {
|
331
333
|
return bad_request(c, 400, __LINE__);
|
332
334
|
}
|
data/ext/agoo/gqleval.c
CHANGED
@@ -21,6 +21,7 @@
|
|
21
21
|
#include "websocket.h"
|
22
22
|
|
23
23
|
#define MAX_RESOLVE_ARGS 16
|
24
|
+
#define MAX_DEPTH 100
|
24
25
|
|
25
26
|
gqlRef gql_root = NULL;
|
26
27
|
gqlType _gql_root_type = NULL;
|
@@ -273,7 +274,10 @@ gql_eval_sels(agooErr err, gqlDoc doc, gqlRef ref, gqlField field, gqlSel sels,
|
|
273
274
|
gqlSel sel;
|
274
275
|
gqlField sf = NULL;
|
275
276
|
|
276
|
-
|
277
|
+
if (MAX_DEPTH < depth) {
|
278
|
+
return agoo_err_set(err, AGOO_ERR_EVAL, "Maximum resolve depth of %d exceeded.", MAX_DEPTH);
|
279
|
+
}
|
280
|
+
depth++;
|
277
281
|
|
278
282
|
for (sel = sels; NULL != sel; sel = sel->next) {
|
279
283
|
if (NULL != field) {
|
@@ -474,8 +478,14 @@ gql_eval_get_hook(agooReq req) {
|
|
474
478
|
result = gql_doc_eval_func(&err, doc);
|
475
479
|
}
|
476
480
|
if (NULL == result) {
|
481
|
+
int code = 500;
|
482
|
+
|
483
|
+
if (err.code < 0) {
|
484
|
+
code = -err.code;
|
485
|
+
}
|
486
|
+
err.code = AGOO_ERR_EVAL;
|
477
487
|
gql_doc_destroy(doc);
|
478
|
-
err_resp(req->res, &err,
|
488
|
+
err_resp(req->res, &err, code);
|
479
489
|
return;
|
480
490
|
}
|
481
491
|
if (GQL_SUBSCRIPTION == doc->op->kind) {
|
@@ -644,7 +654,13 @@ gql_eval_post_hook(agooReq req) {
|
|
644
654
|
indent = (int)strtol(s, NULL, 10);
|
645
655
|
}
|
646
656
|
if (NULL == (result = eval_post(&err, req)) && AGOO_ERR_OK != err.code) {
|
647
|
-
|
657
|
+
int code = 400;
|
658
|
+
|
659
|
+
if (err.code < 0) {
|
660
|
+
code = -err.code;
|
661
|
+
}
|
662
|
+
err.code = AGOO_ERR_EVAL;
|
663
|
+
err_resp(req->res, &err, code);
|
648
664
|
} else if (NULL == result) {
|
649
665
|
value_resp(req, result, 200, indent);
|
650
666
|
} else {
|
data/ext/agoo/gqlintro.c
CHANGED
@@ -1497,13 +1497,13 @@ gql_intro_eval(agooErr err, gqlDoc doc, gqlSel sel, gqlValue result, int depth)
|
|
1497
1497
|
struct _gqlCobj obj;
|
1498
1498
|
|
1499
1499
|
if (0 == strcmp("__type", sel->name)) {
|
1500
|
-
if (
|
1500
|
+
if (2 < depth) {
|
1501
1501
|
return agoo_err_set(err, AGOO_ERR_EVAL, "__type can only be called from a query root.");
|
1502
1502
|
}
|
1503
1503
|
obj.clas = &root_class;
|
1504
1504
|
obj.ptr = NULL;
|
1505
1505
|
} else if (0 == strcmp("__schema", sel->name)) {
|
1506
|
-
if (
|
1506
|
+
if (2 < depth) {
|
1507
1507
|
return agoo_err_set(err, AGOO_ERR_EVAL, "__scheme can only be called from a query root.");
|
1508
1508
|
}
|
1509
1509
|
obj.clas = &root_class;
|
data/ext/agoo/request.c
CHANGED
@@ -684,6 +684,24 @@ call(VALUE self) {
|
|
684
684
|
return io;
|
685
685
|
}
|
686
686
|
|
687
|
+
/* Document-method: set
|
688
|
+
*
|
689
|
+
* call-seq: set()
|
690
|
+
*
|
691
|
+
* Sets an key value pair to the environment of the request.
|
692
|
+
*/
|
693
|
+
static VALUE
|
694
|
+
set(VALUE self, VALUE key, VALUE val) {
|
695
|
+
agooReq r = DATA_PTR(self);
|
696
|
+
|
697
|
+
if (NULL == r) {
|
698
|
+
rb_raise(rb_eArgError, "Request is no longer valid.");
|
699
|
+
}
|
700
|
+
rb_hash_aset((VALUE)r->env, key, val);
|
701
|
+
|
702
|
+
return Qnil;
|
703
|
+
}
|
704
|
+
|
687
705
|
VALUE
|
688
706
|
request_wrap(agooReq req) {
|
689
707
|
// freed from the C side of things
|
@@ -723,6 +741,7 @@ request_init(VALUE mod) {
|
|
723
741
|
rb_define_method(req_class, "body", body, 0);
|
724
742
|
rb_define_method(req_class, "rack_logger", rack_logger, 0);
|
725
743
|
rb_define_method(req_class, "call", call, 0);
|
744
|
+
rb_define_method(req_class, "set", set, 2);
|
726
745
|
|
727
746
|
new_id = rb_intern("new");
|
728
747
|
|
data/ext/agoo/rgraphql.c
CHANGED
@@ -58,8 +58,24 @@ make_ruby_use(agooErr err,
|
|
58
58
|
volatile VALUE v;
|
59
59
|
ID m = rb_intern(method);
|
60
60
|
|
61
|
-
if (!rb_respond_to(root, m)
|
62
|
-
|
61
|
+
if (!rb_respond_to(root, m)) {
|
62
|
+
return AGOO_ERR_OK;
|
63
|
+
}
|
64
|
+
switch (rb_obj_method_arity(root, m)) {
|
65
|
+
case 0:
|
66
|
+
v = rb_funcall(root, m, 0);
|
67
|
+
break;
|
68
|
+
case 1:
|
69
|
+
v = rb_funcall(root, m, 1, Qnil);
|
70
|
+
break;
|
71
|
+
case 2:
|
72
|
+
v = rb_funcall(root, m, 2, Qnil, Qnil);
|
73
|
+
break;
|
74
|
+
default:
|
75
|
+
v = Qnil;
|
76
|
+
break;
|
77
|
+
}
|
78
|
+
if (Qnil == v) {
|
63
79
|
return AGOO_ERR_OK;
|
64
80
|
}
|
65
81
|
if (NULL == (type = gql_type_get(type_name))) {
|
@@ -89,7 +105,13 @@ rescue_error(VALUE x, VALUE ignore) {
|
|
89
105
|
const char *ms = rb_string_value_ptr(&msg);
|
90
106
|
|
91
107
|
agoo_err_set(eval->err, AGOO_ERR_EVAL, "%s: %s", classname, ms);
|
108
|
+
if (rb_respond_to(info, rb_intern("code"))) {
|
109
|
+
VALUE code = rb_funcall(info, rb_intern("code"), 0);
|
92
110
|
|
111
|
+
if (RUBY_T_FIXNUM == rb_type(code)) {
|
112
|
+
eval->err->code = -FIX2INT(code);
|
113
|
+
}
|
114
|
+
}
|
93
115
|
return Qfalse;
|
94
116
|
}
|
95
117
|
|
data/ext/agoo/rserver.c
CHANGED
@@ -48,6 +48,7 @@ static VALUE options_sym;
|
|
48
48
|
static VALUE post_sym;
|
49
49
|
static VALUE push_env_key;
|
50
50
|
static VALUE put_sym;
|
51
|
+
static VALUE patch_sym;
|
51
52
|
|
52
53
|
static VALUE rserver;
|
53
54
|
|
@@ -207,12 +208,12 @@ configure(agooErr err, int port, const char *root, VALUE options) {
|
|
207
208
|
}
|
208
209
|
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("graphql"))))) {
|
209
210
|
const char *path;
|
210
|
-
agooHook dump_hook;
|
211
211
|
agooHook get_hook;
|
212
212
|
agooHook post_hook;
|
213
213
|
agooHook options_hook;
|
214
|
-
|
214
|
+
agooHook head = NULL;
|
215
215
|
long plen;
|
216
|
+
VALUE hide_schema = Qnil;
|
216
217
|
|
217
218
|
rb_check_type(v, T_STRING);
|
218
219
|
if (AGOO_ERR_OK != gql_init(err)) {
|
@@ -220,21 +221,29 @@ configure(agooErr err, int port, const char *root, VALUE options) {
|
|
220
221
|
}
|
221
222
|
path = StringValuePtr(v);
|
222
223
|
plen = (long)RSTRING_LEN(v);
|
223
|
-
if ((int)sizeof(schema_path) - 8 < plen) {
|
224
|
-
rb_raise(rb_eArgError, "A graphql schema path is limited to %d characters.", (int)(sizeof(schema_path) - 8));
|
225
|
-
}
|
226
|
-
memcpy(schema_path, path, plen);
|
227
|
-
memcpy(schema_path + plen, "/schema", 8);
|
228
224
|
|
229
|
-
dump_hook = agoo_hook_func_create(AGOO_GET, schema_path, gql_dump_hook, &agoo_server.eval_queue);
|
230
225
|
get_hook = agoo_hook_func_create(AGOO_GET, path, gql_eval_get_hook, &agoo_server.eval_queue);
|
231
226
|
post_hook = agoo_hook_func_create(AGOO_POST, path, gql_eval_post_hook, &agoo_server.eval_queue);
|
232
227
|
options_hook = agoo_hook_func_create(AGOO_OPTIONS, path, gql_eval_options_hook, &agoo_server.eval_queue);
|
233
|
-
|
228
|
+
if (Qnil != (hide_schema = rb_hash_lookup(options, ID2SYM(rb_intern("hide_schema")))) && Qtrue == hide_schema) {
|
229
|
+
head = get_hook;
|
230
|
+
} else {
|
231
|
+
char schema_path[256];
|
232
|
+
agooHook dump_hook;
|
233
|
+
|
234
|
+
if ((int)sizeof(schema_path) - 8 < plen) {
|
235
|
+
rb_raise(rb_eArgError, "A graphql schema path is limited to %d characters.", (int)(sizeof(schema_path) - 8));
|
236
|
+
}
|
237
|
+
memcpy(schema_path, path, plen);
|
238
|
+
memcpy(schema_path + plen, "/schema", 8);
|
239
|
+
dump_hook = agoo_hook_func_create(AGOO_GET, schema_path, gql_dump_hook, &agoo_server.eval_queue);
|
240
|
+
dump_hook->next = get_hook;
|
241
|
+
head = dump_hook;
|
242
|
+
}
|
234
243
|
get_hook->next = post_hook;
|
235
244
|
post_hook->next = options_hook;
|
236
245
|
options_hook->next = agoo_server.hooks;
|
237
|
-
agoo_server.hooks =
|
246
|
+
agoo_server.hooks = head;
|
238
247
|
}
|
239
248
|
if (Qnil != (v = rb_hash_lookup(options, ID2SYM(rb_intern("quiet"))))) {
|
240
249
|
if (Qtrue == v) {
|
@@ -295,6 +304,8 @@ configure(agooErr err, int port, const char *root, VALUE options) {
|
|
295
304
|
* - *:ssl_sert* [_String_] filepath to the SSL certificate file.
|
296
305
|
*
|
297
306
|
* - *:ssl_key* [_String_] filepath to the SSL private key file.
|
307
|
+
*
|
308
|
+
* - *:hide_schema* [_true_|_false_] if true the graphql/schema path is handled.
|
298
309
|
*/
|
299
310
|
static VALUE
|
300
311
|
rserver_init(int argc, VALUE *argv, VALUE self) {
|
@@ -971,6 +982,8 @@ handle(VALUE self, VALUE method, VALUE pattern, VALUE handler) {
|
|
971
982
|
meth = AGOO_POST;
|
972
983
|
} else if (put_sym == method) {
|
973
984
|
meth = AGOO_PUT;
|
985
|
+
} else if (patch_sym == method) {
|
986
|
+
meth = AGOO_PATCH;
|
974
987
|
} else if (Qnil == method) {
|
975
988
|
meth = AGOO_ALL;
|
976
989
|
} else {
|
@@ -1277,6 +1290,7 @@ server_init(VALUE mod) {
|
|
1277
1290
|
options_sym = ID2SYM(rb_intern("OPTIONS")); rb_gc_register_address(&options_sym);
|
1278
1291
|
post_sym = ID2SYM(rb_intern("POST")); rb_gc_register_address(&post_sym);
|
1279
1292
|
put_sym = ID2SYM(rb_intern("PUT")); rb_gc_register_address(&put_sym);
|
1293
|
+
patch_sym = ID2SYM(rb_intern("PATCH")); rb_gc_register_address(&patch_sym);
|
1280
1294
|
|
1281
1295
|
push_env_key = rb_str_new_cstr("rack.upgrade"); rb_gc_register_address(&push_env_key);
|
1282
1296
|
|
data/lib/agoo/version.rb
CHANGED
data/test/graphql_test.rb
CHANGED
@@ -458,6 +458,32 @@ fragment basic on Artist {
|
|
458
458
|
post_test(uri, body, 'application/graphql', expect)
|
459
459
|
end
|
460
460
|
|
461
|
+
def test_post_fragment_loop
|
462
|
+
uri = URI('http://localhost:6472/graphql?indent=2')
|
463
|
+
body = %^
|
464
|
+
{
|
465
|
+
artist(name:"Fazerdaze") {
|
466
|
+
...loop
|
467
|
+
}
|
468
|
+
}
|
469
|
+
|
470
|
+
fragment loop on Artist {
|
471
|
+
name
|
472
|
+
...loop
|
473
|
+
}
|
474
|
+
^
|
475
|
+
expect = %^{
|
476
|
+
"errors":[
|
477
|
+
{
|
478
|
+
"message":"Maximum resolve depth of 100 exceeded.",
|
479
|
+
"code":"eval error"
|
480
|
+
}
|
481
|
+
]
|
482
|
+
}
|
483
|
+
^
|
484
|
+
post_test(uri, body, 'application/graphql', expect, 'errors.0.timestamp')
|
485
|
+
end
|
486
|
+
|
461
487
|
def test_post_json_fragment
|
462
488
|
uri = URI('http://localhost:6472/graphql?indent=2')
|
463
489
|
body = %^{
|
@@ -1044,7 +1070,7 @@ mutation {
|
|
1044
1070
|
assert_equal(expect, content)
|
1045
1071
|
end
|
1046
1072
|
|
1047
|
-
def post_test(uri, body, content_type, expect)
|
1073
|
+
def post_test(uri, body, content_type, expect, ignore=nil)
|
1048
1074
|
uri = URI(uri)
|
1049
1075
|
req = Net::HTTP::Post.new(uri)
|
1050
1076
|
req['Accept-Encoding'] = '*'
|
@@ -1055,6 +1081,11 @@ mutation {
|
|
1055
1081
|
}
|
1056
1082
|
content = res.body
|
1057
1083
|
assert_equal('application/json', res['Content-Type'])
|
1084
|
+
unless ignore.nil?
|
1085
|
+
result = Oj.load(content, mode: :strict)
|
1086
|
+
deep_delete(result, ignore.split('.'))
|
1087
|
+
content = Oj.dump(result, indent: 2)
|
1088
|
+
end
|
1058
1089
|
assert_equal(expect, content)
|
1059
1090
|
end
|
1060
1091
|
end
|
data/test/rack_handler_test.rb
CHANGED
@@ -33,7 +33,7 @@ size=big
|
|
33
33
|
]
|
34
34
|
elsif 'POST' == req['REQUEST_METHOD']
|
35
35
|
[ 204, { }, [] ]
|
36
|
-
elsif 'PUT' == req['REQUEST_METHOD']
|
36
|
+
elsif 'PUT' == req['REQUEST_METHOD'] || 'PATCH' == req['REQUEST_METHOD']
|
37
37
|
[ 201,
|
38
38
|
{ },
|
39
39
|
[ req['rack.input'].read ]
|
@@ -65,6 +65,7 @@ size=big
|
|
65
65
|
Agoo::Server.handle(:GET, "/tellme", handler)
|
66
66
|
Agoo::Server.handle(:POST, "/makeme", handler)
|
67
67
|
Agoo::Server.handle(:PUT, "/makeme", handler)
|
68
|
+
Agoo::Server.handle(:PATCH, "/makeme", handler)
|
68
69
|
|
69
70
|
Agoo::Server.start()
|
70
71
|
|
@@ -156,4 +157,20 @@ size=big
|
|
156
157
|
assert_equal('hello', res.body)
|
157
158
|
end
|
158
159
|
|
160
|
+
def test_patch
|
161
|
+
uri = URI('http://localhost:6467/makeme')
|
162
|
+
req = Net::HTTP::Patch.new(uri)
|
163
|
+
# Set the headers the way we want them.
|
164
|
+
req['Accept-Encoding'] = '*'
|
165
|
+
req['Accept'] = 'application/json'
|
166
|
+
req['User-Agent'] = 'Ruby'
|
167
|
+
req.body = 'hello'
|
168
|
+
|
169
|
+
res = Net::HTTP.start(uri.hostname, uri.port) { |h|
|
170
|
+
h.request(req)
|
171
|
+
}
|
172
|
+
assert_equal(Net::HTTPCreated, res.class)
|
173
|
+
assert_equal('hello', res.body)
|
174
|
+
end
|
175
|
+
|
159
176
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: agoo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.15.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Ohler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|