agoo 2.14.2 → 2.15.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e538b3f1c7ed9ea23d8529c29bf5104d0882b0dcda49e6114bb011491b1e3ed7
4
- data.tar.gz: ee1a1284779ec7108675794f4eff89f826c76f3a5e10a7f07a42bab8bbae31cb
3
+ metadata.gz: 74cca7bbc7156c967212d01e349f2d9134af318c8abd59687167fa5580773e54
4
+ data.tar.gz: cf765b3109277f2caa4fbb2c25d5352990fd03a394a78de82cf81ed4b235b6f4
5
5
  SHA512:
6
- metadata.gz: e28b50c1bea0934691679719532108fdd6d912f3beed8b9eabe6071ccd71af393b14f3c81d094cb82b5777a22884cc716e12d3c66629e39c5f7f1290919fc030
7
- data.tar.gz: 1d478668d82b79acc3d2cabb56fe9aca55fb3513b4c5a5810c0d200a69b081573ff532a346c13d513fae0980fa6331f3701ee041d43ea7cfc32cd2d2a586d0c2
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
- // TBD if depth over max then return an error
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, 500);
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
- err_resp(req->res, &err, 400);
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 (1 < depth) {
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 (1 < depth) {
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
- Qnil == (v = rb_funcall(root, m, 0))) {
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
- char schema_path[256];
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
- dump_hook->next = get_hook;
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 = dump_hook;
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
@@ -1,5 +1,5 @@
1
1
 
2
2
  module Agoo
3
3
  # Agoo version.
4
- VERSION = '2.14.2'
4
+ VERSION = '2.15.1'
5
5
  end
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
@@ -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.14.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-02-22 00:00:00.000000000 Z
11
+ date: 2022-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj