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 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