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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06273c8f4d9d00c382ed4d0abec44bc54b24a1063cb7cc15796137153a169206
4
- data.tar.gz: ba1e9f93e1cbd39e2777ce5a0d9e61498f5718c3742ad001d28490eec848e838
3
+ metadata.gz: df42da153e97e018d4984fa75be78ac86a1c8b97aed6b48d62ec7fe5047d7099
4
+ data.tar.gz: e83a55eed4781bbb0ab08df41203509df6ec45d06817b87c9d61d25bca8a294a
5
5
  SHA512:
6
- metadata.gz: ccf05a2fed955f9e44461a6557c7d3232280c0fd316d523bbbb97cfc9942ef8407a4f31e92b4ba888d3d895578d85753d12e2966aeb08d8725a4de6f5c74eb2f
7
- data.tar.gz: 3af05ec64f0af3ab57bbb0c95652d8050ca6c73d0cf39777cfeff8e5c0ac2cc71ddc783b4d0fecc5d675e865616fe8dee7c145268359e51e5bc7864f00428733
6
+ metadata.gz: ac6e8d86a9b545d93c9ee033950422292b4ab2f740ba692f2c24940e928c42466b895dfae6ce5263040f2b53613a6c1241f5dfe5c0be21961f1ee885970ec220
7
+ data.tar.gz: 9dd8414c88948198bd1a22d5aac236ef8d5bf4251020b7fcfc4b94837d8a48433aa6774ba9e13df1dc36dc1e29f1697dfcc2c21e8ef1a9dc6897d223b72eb946
@@ -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
@@ -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
@@ -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 == strncmp("tcp://", url, 6)) {
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 == strncmp("http://", url, 7)) {
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 == strncmp("unix://", url, 7)) {
238
+ if (0 == strncasecmp("unix://", url, 7)) {
239
239
  return url_named(err, url + 7);
240
240
  }
241
- if (0 == strncmp("https://", url, 8)) {
241
+ if (0 == strncasecmp("https://", url, 8)) {
242
242
  return url_ssl(err, url + 8);
243
243
  }
244
- if (0 == strncmp("ssl://", url, 6)) {
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;
@@ -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 == strncmp(key, h, klen) && ':' == h[klen]) {
176
+ if (0 == strncasecmp(key, h, klen) && ':' == h[klen]) {
177
177
  h += klen + 1;
178
178
  for (; ' ' == *h; h++) {
179
179
  }
@@ -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
- return;
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
- err_resp(res, &err, 500);
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 == (text = agoo_text_prepend(text, buf, cnt))) {
112
- agoo_log_cat(&agoo_error_cat, "Failed to allocate memory for a response.");
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 == strncmp(graphql_content_type, s, sizeof(graphql_content_type) - 1)) {
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 == strncmp(json_content_type, s, sizeof(json_content_type) - 1)) {
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
- if (GQL_SCALAR_OBJECT != m->value->type->scalar_kind) {
536
- agoo_err_set(err, AGOO_ERR_EVAL, "expected variables to be an object.");
537
- goto DONE;
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
- link->value = NULL;
543
- if (NULL == v) {
544
- goto DONE;
589
+ link->value = NULL;
590
+ if (NULL == v) {
591
+ goto DONE;
592
+ }
593
+ v->next = vars;
594
+ vars = v;
545
595
  }
546
- v->next = vars;
547
- vars = v;
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
- gql_doc_destroy(doc);
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;