agoo 2.5.7 → 2.6.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.

Potentially problematic release.


This version of agoo might be problematic. Click here for more details.

Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +38 -0
  4. data/ext/agoo/agoo.c +11 -2
  5. data/ext/agoo/bind.c +15 -20
  6. data/ext/agoo/con.c +32 -25
  7. data/ext/agoo/debug.c +225 -162
  8. data/ext/agoo/debug.h +31 -51
  9. data/ext/agoo/doc.c +278 -5
  10. data/ext/agoo/doc.h +6 -1
  11. data/ext/agoo/err.c +1 -0
  12. data/ext/agoo/err.h +1 -0
  13. data/ext/agoo/error_stream.c +3 -6
  14. data/ext/agoo/gqlcobj.c +12 -0
  15. data/ext/agoo/gqlcobj.h +25 -0
  16. data/ext/agoo/gqleval.c +520 -0
  17. data/ext/agoo/gqleval.h +49 -0
  18. data/ext/agoo/gqlintro.c +1237 -97
  19. data/ext/agoo/gqlintro.h +8 -0
  20. data/ext/agoo/gqljson.c +460 -0
  21. data/ext/agoo/gqljson.h +15 -0
  22. data/ext/agoo/gqlvalue.c +679 -136
  23. data/ext/agoo/gqlvalue.h +29 -7
  24. data/ext/agoo/graphql.c +841 -362
  25. data/ext/agoo/graphql.h +180 -90
  26. data/ext/agoo/hook.c +8 -16
  27. data/ext/agoo/http.c +3 -4
  28. data/ext/agoo/log.c +22 -25
  29. data/ext/agoo/log.h +1 -0
  30. data/ext/agoo/page.c +24 -40
  31. data/ext/agoo/pub.c +23 -21
  32. data/ext/agoo/queue.c +2 -4
  33. data/ext/agoo/ready.c +9 -9
  34. data/ext/agoo/req.c +80 -5
  35. data/ext/agoo/req.h +2 -0
  36. data/ext/agoo/res.c +1 -3
  37. data/ext/agoo/rgraphql.c +753 -0
  38. data/ext/agoo/rresponse.c +9 -15
  39. data/ext/agoo/rserver.c +18 -17
  40. data/ext/agoo/sdl.c +1264 -120
  41. data/ext/agoo/sdl.h +8 -1
  42. data/ext/agoo/sectime.c +136 -0
  43. data/ext/agoo/sectime.h +19 -0
  44. data/ext/agoo/server.c +1 -3
  45. data/ext/agoo/subject.c +2 -4
  46. data/ext/agoo/text.c +124 -18
  47. data/ext/agoo/text.h +5 -1
  48. data/ext/agoo/upgraded.c +2 -4
  49. data/lib/agoo/version.rb +1 -1
  50. data/test/base_handler_test.rb +43 -40
  51. data/test/bind_test.rb +49 -48
  52. data/test/graphql_test.rb +1019 -0
  53. data/test/hijack_test.rb +1 -1
  54. data/test/rack_handler_test.rb +40 -34
  55. data/test/static_test.rb +33 -32
  56. metadata +17 -6
@@ -13,10 +13,9 @@
13
13
  agooReq
14
14
  agoo_req_create(size_t mlen) {
15
15
  size_t size = mlen + sizeof(struct _agooReq) - 7;
16
- agooReq req = (agooReq)malloc(size);
16
+ agooReq req = (agooReq)AGOO_MALLOC(size);
17
17
 
18
18
  if (NULL != req) {
19
- DEBUG_ALLOC(mem_req, req);
20
19
  memset(req, 0, size);
21
20
  req->env = agoo_server.env_nil_value;
22
21
  req->mlen = mlen;
@@ -27,11 +26,10 @@ agoo_req_create(size_t mlen) {
27
26
 
28
27
  void
29
28
  agoo_req_destroy(agooReq req) {
30
- DEBUG_FREE(mem_req, req);
31
29
  if (NULL != req->hook && PUSH_HOOK == req->hook->type) {
32
- free(req->hook);
30
+ AGOO_FREE(req->hook);
33
31
  }
34
- free(req);
32
+ AGOO_FREE(req);
35
33
  }
36
34
 
37
35
  const char*
@@ -93,3 +91,80 @@ agoo_req_query_value(agooReq r, const char *key, int klen, int *vlenp) {
93
91
  return value;
94
92
  }
95
93
 
94
+ static int
95
+ hexVal(int c) {
96
+ int h = -1;
97
+
98
+ if ('0' <= c && c <= '9') {
99
+ h = c - '0';
100
+ } else if ('a' <= c && c <= 'f') {
101
+ h = c - 'a' + 10;
102
+ } else if ('A' <= c && c <= 'F') {
103
+ h = c - 'A' + 10;
104
+ }
105
+ return h;
106
+ }
107
+
108
+ int
109
+ agoo_req_query_decode(char *s, int len) {
110
+ char *sn = s;
111
+ char *so = s;
112
+ char *end = s + len;
113
+
114
+ while (so < end) {
115
+ if ('%' == *so) {
116
+ int n;
117
+ int c = 0;
118
+
119
+ so++;
120
+ if (0 > (c = hexVal(*so))) {
121
+ *sn++ = '%';
122
+ continue;
123
+ }
124
+ so++;
125
+ if (0 > (n = hexVal(*so))) {
126
+ continue;
127
+ }
128
+ c = (c << 4) + n;
129
+ so++;
130
+ *sn++ = (char)c;
131
+ } else {
132
+ *sn++ = *so++;
133
+ }
134
+ }
135
+ *sn = '\0';
136
+
137
+ return (int)(sn - s);
138
+ }
139
+
140
+ const char*
141
+ agoo_req_header_value(agooReq req, const char *key, int *vlen) {
142
+ // Search for \r then check for \n and then the key followed by a :. Keep
143
+ // trying until the end of the header.
144
+ const char *h = req->header.start;
145
+ const char *hend = h + req->header.len;
146
+ const char *value;
147
+ int klen = (int)strlen(key);
148
+
149
+ while (h < hend) {
150
+ if (0 == strncmp(key, h, klen) && ':' == h[klen]) {
151
+ h += klen + 1;
152
+ for (; ' ' == *h; h++) {
153
+ }
154
+ value = h;
155
+ for (; '\r' != *h && '\0' != *h; h++) {
156
+ }
157
+ *vlen = (int)(h - value);
158
+
159
+ return value;
160
+ }
161
+ for (; h < hend; h++) {
162
+ if ('\r' == *h && '\n' == *(h + 1)) {
163
+ h += 2;
164
+ break;
165
+ }
166
+ }
167
+ }
168
+ return NULL;
169
+ }
170
+
@@ -43,5 +43,7 @@ extern void agoo_req_destroy(agooReq req);
43
43
  extern const char* agoo_req_host(agooReq r, int *lenp);
44
44
  extern int agoo_req_port(agooReq r);
45
45
  extern const char* agoo_req_query_value(agooReq r, const char *key, int klen, int *vlenp);
46
+ extern int agoo_req_query_decode(char *s, int len);
47
+ const char* agoo_req_header_value(agooReq req, const char *key, int *vlen);
46
48
 
47
49
  #endif // AGOO_REQ_H
@@ -21,10 +21,9 @@ agoo_res_create(agooCon con) {
21
21
  pthread_mutex_unlock(&con->loop->lock);
22
22
 
23
23
  if (NULL == res) {
24
- if (NULL == (res = (agooRes)malloc(sizeof(struct _agooRes)))) {
24
+ if (NULL == (res = (agooRes)AGOO_MALLOC(sizeof(struct _agooRes)))) {
25
25
  return NULL;
26
26
  }
27
- DEBUG_ALLOC(mem_res, res)
28
27
  }
29
28
  res->next = NULL;
30
29
  atomic_init(&res->message, NULL);
@@ -45,7 +44,6 @@ agoo_res_destroy(agooRes res) {
45
44
  if (NULL != message) {
46
45
  agoo_text_release(message);
47
46
  }
48
-
49
47
  res->next = NULL;
50
48
  pthread_mutex_lock(&res->con->loop->lock);
51
49
  if (NULL == res->con->loop->res_tail) {
@@ -0,0 +1,753 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #include <stdint.h>
4
+ #include <stdlib.h>
5
+
6
+ #include <ruby.h>
7
+ #include <ruby/thread.h>
8
+
9
+ #include "err.h"
10
+ #include "gqleval.h"
11
+ #include "gqlintro.h"
12
+ #include "gqlvalue.h"
13
+ #include "graphql.h"
14
+ #include "sdl.h"
15
+
16
+ static VALUE graphql_class = Qundef;
17
+
18
+ typedef struct _eval {
19
+ gqlDoc doc;
20
+ agooErr err;
21
+ gqlValue value;
22
+ } *Eval;
23
+
24
+ typedef struct _typeClass {
25
+ gqlType type;
26
+ const char *classname;
27
+ } *TypeClass;
28
+
29
+ static TypeClass type_class_map = NULL;
30
+
31
+ static int
32
+ make_ruby_use(agooErr err, VALUE root, const char *method, const char *type_name) {
33
+ gqlType type;
34
+ gqlDirUse use;
35
+ volatile VALUE v;
36
+ ID m = rb_intern(method);
37
+
38
+ if (!rb_respond_to(root, m) ||
39
+ Qnil == (v = rb_funcall(root, m, 0))) {
40
+ return AGOO_ERR_OK;
41
+ }
42
+ if (NULL == (type = gql_type_get(type_name))) {
43
+ return agoo_err_set(err, AGOO_ERR_ARG, "Failed to find the '%s' type.", type_name);
44
+ }
45
+ if (NULL == (use = gql_dir_use_create(err, "ruby"))) {
46
+ return err->code;
47
+ }
48
+ if (AGOO_ERR_OK != gql_dir_use_arg(err, use, "class", gql_string_create(err, rb_obj_classname(v), 0))) {
49
+ return err->code;
50
+ }
51
+ gql_type_directive_use(type, use);
52
+
53
+ return AGOO_ERR_OK;
54
+ }
55
+
56
+ static VALUE
57
+ rescue_error(VALUE x) {
58
+ Eval eval = (Eval)x;
59
+ volatile VALUE info = rb_errinfo();
60
+ volatile VALUE msg = rb_funcall(info, rb_intern("message"), 0);
61
+ const char *classname = rb_obj_classname(info);
62
+ const char *ms = rb_string_value_ptr(&msg);
63
+
64
+ agoo_err_set(eval->err, AGOO_ERR_EVAL, "%s: %s", classname, ms);
65
+
66
+ return Qfalse;
67
+ }
68
+
69
+ static VALUE
70
+ call_eval(void *x) {
71
+ Eval eval = (Eval)x;
72
+
73
+ eval->value = gql_doc_eval(eval->err, eval->doc);
74
+
75
+ return Qnil;
76
+ }
77
+
78
+ static void*
79
+ protect_eval(void *x) {
80
+ rb_rescue2(call_eval, (VALUE)x, rescue_error, (VALUE)x, rb_eException, 0);
81
+
82
+ return NULL;
83
+ }
84
+
85
+ static gqlValue
86
+ eval_wrap(agooErr err, gqlDoc doc) {
87
+ struct _eval eval = {
88
+ .doc = doc,
89
+ .err = err,
90
+ .value = NULL,
91
+ };
92
+
93
+ rb_thread_call_with_gvl(protect_eval, &eval);
94
+
95
+ return eval.value;
96
+ }
97
+
98
+ static VALUE
99
+ gval_to_ruby(gqlValue value) {
100
+ volatile VALUE rval = Qnil;
101
+
102
+ if (NULL == value || NULL == value->type) {
103
+ return Qnil;
104
+ }
105
+ if (GQL_SCALAR == value->type->kind) {
106
+ switch (value->type->scalar_kind) {
107
+ case GQL_SCALAR_BOOL:
108
+ rval = value->b ? Qtrue : Qfalse;
109
+ break;
110
+ case GQL_SCALAR_INT:
111
+ rval = RB_INT2NUM(value->i);
112
+ break;
113
+ case GQL_SCALAR_I64:
114
+ rval = RB_LONG2NUM(value->i64);
115
+ break;
116
+ case GQL_SCALAR_FLOAT:
117
+ rval = DBL2NUM(value->f);
118
+ break;
119
+ case GQL_SCALAR_STRING:
120
+ case GQL_SCALAR_TOKEN:
121
+ rval = rb_str_new_cstr(gql_string_get(value));
122
+ break;
123
+ case GQL_SCALAR_TIME: {
124
+ time_t secs = (time_t)(value->time / 1000000000LL);
125
+
126
+ rval = rb_time_nano_new(secs, (long)(value->time - secs * 1000000000LL));
127
+ break;
128
+ }
129
+ case GQL_SCALAR_UUID: {
130
+ char buf[64];
131
+
132
+ sprintf(buf, "%08lx-%04lx-%04lx-%04lx-%012lx",
133
+ (unsigned long)(value->uuid.hi >> 32),
134
+ (unsigned long)((value->uuid.hi >> 16) & 0x000000000000FFFFUL),
135
+ (unsigned long)(value->uuid.hi & 0x000000000000FFFFUL),
136
+ (unsigned long)(value->uuid.lo >> 48),
137
+ (unsigned long)(value->uuid.lo & 0x0000FFFFFFFFFFFFUL));
138
+ rval = rb_str_new_cstr(buf);
139
+ break;
140
+ }
141
+ case GQL_SCALAR_LIST: {
142
+ gqlLink link;
143
+
144
+ rval = rb_ary_new();
145
+ for (link = value->members; NULL != link; link = link->next) {
146
+ rb_ary_push(rval, gval_to_ruby(link->value));
147
+ }
148
+ break;
149
+ }
150
+ case GQL_SCALAR_OBJECT: {
151
+ gqlLink link;
152
+
153
+ rval = rb_hash_new();
154
+ for (link = value->members; NULL != link; link = link->next) {
155
+ rb_hash_aset(rval, rb_str_new_cstr(link->key), gval_to_ruby(link->value));
156
+
157
+ }
158
+ break;
159
+ }
160
+ default:
161
+ break;
162
+ }
163
+ } else if (GQL_ENUM == value->type->kind) {
164
+ rval = rb_str_new_cstr(gql_string_get(value));
165
+ }
166
+ return rval;
167
+ }
168
+
169
+ static VALUE
170
+ ref_to_string(gqlRef ref) {
171
+ volatile VALUE value;
172
+
173
+ if (T_STRING == rb_type((VALUE)ref)) {
174
+ value = (VALUE)ref;
175
+ } else {
176
+ value = rb_funcall((VALUE)ref, rb_intern("to_s"), 0);
177
+ }
178
+ return value;
179
+ }
180
+
181
+ static gqlValue
182
+ time_to_time(agooErr err, VALUE rval) {
183
+ long long sec;
184
+ long long nsec;
185
+
186
+ #ifdef HAVE_RB_TIME_TIMESPEC
187
+ // rb_time_timespec as well as rb_time_timeeval have a bug that causes an
188
+ // exception to be raised if a time is before 1970 on 32 bit systems so
189
+ // check the timespec size and use the ruby calls if a 32 bit system.
190
+ if (16 <= sizeof(struct timespec)) {
191
+ struct timespec ts = rb_time_timespec(rval);
192
+
193
+ sec = (long long)ts.tv_sec;
194
+ nsec = ts.tv_nsec;
195
+ } else {
196
+ sec = rb_num2ll(rb_funcall2(rval, oj_tv_sec_id, 0, 0));
197
+ nsec = rb_num2ll(rb_funcall2(rval, oj_tv_nsec_id, 0, 0));
198
+ }
199
+ #else
200
+ sec = rb_num2ll(rb_funcall2(rval, rb_intern("tv_sec"), 0, 0));
201
+ nsec = rb_num2ll(rb_funcall2(rval, rb_intern("tv_nsec"), 0, 0));
202
+ #endif
203
+ return gql_time_create(err, (int64_t)(sec * 1000000000LL + nsec));
204
+ }
205
+
206
+ static gqlValue
207
+ coerce(agooErr err, gqlRef ref, gqlType type) {
208
+ gqlValue value = NULL;
209
+ volatile VALUE v;
210
+
211
+ if (NULL == type) {
212
+ // This is really an error but make a best effort anyway.
213
+ switch (rb_type((VALUE)ref)) {
214
+ case RUBY_T_FLOAT:
215
+ value = gql_float_create(err, rb_num2dbl(rb_to_float((VALUE)ref)));
216
+ break;
217
+ case RUBY_T_STRING:
218
+ v = ref_to_string(ref);
219
+ value = gql_string_create(err, rb_string_value_ptr(&v), RSTRING_LEN(v));
220
+ break;
221
+ case RUBY_T_ARRAY: {
222
+ gqlValue v;
223
+ VALUE a = (VALUE)ref;
224
+ int cnt = (int)RARRAY_LEN(a);
225
+ int i;
226
+
227
+ value = gql_list_create(err, NULL);
228
+ for (i = 0; i < cnt; i++) {
229
+ if (NULL == (v = coerce(err, (gqlRef)rb_ary_entry(a, i), type->base)) ||
230
+ AGOO_ERR_OK != gql_list_append(err, value, v)) {
231
+ return NULL;
232
+ }
233
+ }
234
+ break;
235
+ }
236
+ case RUBY_T_TRUE:
237
+ value = gql_bool_create(err, true);
238
+ break;
239
+ case RUBY_T_FALSE:
240
+ value = gql_bool_create(err, false);
241
+ break;
242
+ case RUBY_T_SYMBOL:
243
+ value = gql_string_create(err, rb_id2name(rb_sym2id((VALUE)ref)), -1);
244
+ break;
245
+ case RUBY_T_FIXNUM: {
246
+ long i = RB_NUM2LONG(rb_to_int((VALUE)ref));
247
+
248
+ if (i < INT32_MIN || INT32_MAX < i) {
249
+ value = gql_i64_create(err, i);
250
+ } else {
251
+ value = gql_int_create(err, i);
252
+ }
253
+ break;
254
+ }
255
+ case RUBY_T_NIL:
256
+ value = gql_null_create(err);
257
+ break;
258
+ case RUBY_T_OBJECT: {
259
+ VALUE clas = rb_obj_class((VALUE)ref);
260
+
261
+ if (rb_cTime == clas) {
262
+ value = time_to_time(err, (VALUE)ref);
263
+ }
264
+ break;
265
+ }
266
+ default:
267
+ v = ref_to_string(ref);
268
+ value = gql_string_create(err, rb_string_value_ptr(&v), RSTRING_LEN(v));
269
+ break;
270
+ }
271
+ } else if (GQL_SCALAR == type->kind) {
272
+ switch (type->scalar_kind) {
273
+ case GQL_SCALAR_BOOL:
274
+ if (Qtrue == (VALUE)ref) {
275
+ value = gql_bool_create(err, true);
276
+ } else if (Qfalse == (VALUE)ref) {
277
+ value = gql_bool_create(err, false);
278
+ }
279
+ break;
280
+ case GQL_SCALAR_INT:
281
+ value = gql_int_create(err, RB_NUM2INT(rb_to_int((VALUE)ref)));
282
+ break;
283
+ case GQL_SCALAR_I64:
284
+ value = gql_i64_create(err, RB_NUM2LONG(rb_to_int((VALUE)ref)));
285
+ break;
286
+ case GQL_SCALAR_FLOAT:
287
+ value = gql_float_create(err, rb_num2dbl(rb_to_float((VALUE)ref)));
288
+ break;
289
+ case GQL_SCALAR_STRING:
290
+ case GQL_SCALAR_TOKEN:
291
+ v = ref_to_string(ref);
292
+ value = gql_string_create(err, rb_string_value_ptr(&v), RSTRING_LEN(v));
293
+ break;
294
+ case GQL_SCALAR_TIME: {
295
+ VALUE clas = rb_obj_class((VALUE)ref);
296
+
297
+ if (rb_cTime == clas) {
298
+ value = time_to_time(err, (VALUE)ref);
299
+ }
300
+ break;
301
+ }
302
+ case GQL_SCALAR_UUID:
303
+ v = ref_to_string(ref);
304
+ value = gql_uuid_str_create(err, rb_string_value_ptr(&v), RSTRING_LEN(v));
305
+ break;
306
+ case GQL_SCALAR_LIST: {
307
+ gqlValue v;
308
+ VALUE a = (VALUE)ref;
309
+ int cnt = (int)RARRAY_LEN(a);
310
+ int i;
311
+
312
+ value = gql_list_create(err, NULL);
313
+ for (i = 0; i < cnt; i++) {
314
+
315
+ if (NULL == (v =coerce(err, (gqlRef)rb_ary_entry(a, i), type->base)) ||
316
+ AGOO_ERR_OK != gql_list_append(err, value, v)) {
317
+ return NULL;
318
+ }
319
+ }
320
+ break;
321
+ }
322
+ default:
323
+ break;
324
+ }
325
+ if (NULL == value) {
326
+ if (AGOO_ERR_OK == err->code) {
327
+ agoo_err_set(err, AGOO_ERR_EVAL, "Failed to coerce a %s into a %s.", rb_obj_classname((VALUE)ref), type->name);
328
+ }
329
+ }
330
+ } else if (GQL_ENUM == type->kind) {
331
+ v = ref_to_string(ref);
332
+ value = gql_string_create(err, rb_string_value_ptr(&v), RSTRING_LEN(v));
333
+ } else {
334
+ rb_raise(rb_eStandardError, "Can not coerce a non-scalar into a %s.", type->name);
335
+ }
336
+ return value;
337
+ }
338
+
339
+ static gqlType
340
+ ref_type(gqlRef ref) {
341
+ gqlType type = NULL;
342
+
343
+ if (NULL != type_class_map) {
344
+ TypeClass tc;
345
+ const char *classname = rb_obj_classname((VALUE)ref);
346
+
347
+ for (tc = type_class_map; NULL != tc->type; tc++) {
348
+ if (0 == strcmp(classname, tc->classname)) {
349
+ type = tc->type;
350
+ break;
351
+ }
352
+ }
353
+ }
354
+ return type;
355
+ }
356
+
357
+ static int
358
+ resolve(agooErr err, gqlDoc doc, gqlRef target, gqlField field, gqlSel sel, gqlValue result, int depth) {
359
+ volatile VALUE child;
360
+ VALUE obj = (VALUE)target;
361
+ int d2 = depth + 1;
362
+ const char *key = sel->name;
363
+
364
+ if ('_' == *sel->name && '_' == sel->name[1]) {
365
+ if (0 == strcmp("__typename", sel->name)) {
366
+ if (AGOO_ERR_OK != gql_set_typename(err, ref_type(target), key, result)) {
367
+ return err->code;
368
+ }
369
+ return AGOO_ERR_OK;
370
+ }
371
+ switch (doc->op->kind) {
372
+ case GQL_QUERY:
373
+ return gql_intro_eval(err, doc, sel, result, depth);
374
+ case GQL_MUTATION:
375
+ return agoo_err_set(err, AGOO_ERR_EVAL, "%s can not be called on a mutation.", sel->name);
376
+ case GQL_SUBSCRIPTION:
377
+ return agoo_err_set(err, AGOO_ERR_EVAL, "%s can not be called on a subscription.", sel->name);
378
+ default:
379
+ return agoo_err_set(err, AGOO_ERR_EVAL, "Not a valid operation on the root object.");
380
+ }
381
+ }
382
+ if (NULL == sel->args) {
383
+ child = rb_funcall(obj, rb_intern(sel->name), 0);
384
+ } else {
385
+ volatile VALUE rargs = rb_hash_new();
386
+ gqlSelArg sa;
387
+ gqlValue v;
388
+
389
+ for (sa = sel->args; NULL != sa; sa = sa->next) {
390
+ if (NULL != sa->var) {
391
+ v = sa->var->value;
392
+ } else {
393
+ v = sa->value;
394
+ }
395
+ if (NULL != field) {
396
+ gqlArg fa;
397
+
398
+ for (fa = field->args; NULL != fa; fa = fa->next) {
399
+ if (0 == strcmp(sa->name, fa->name)) {
400
+ if (v->type != fa->type && GQL_SCALAR_VAR != v->type->scalar_kind) {
401
+ if (AGOO_ERR_OK != gql_value_convert(err, v, fa->type)) {
402
+ return err->code;
403
+ }
404
+ }
405
+ break;
406
+ }
407
+ }
408
+ }
409
+ rb_hash_aset(rargs, rb_str_new_cstr(sa->name), gval_to_ruby(v));
410
+ }
411
+ child = rb_funcall(obj, rb_intern(sel->name), 1, rargs);
412
+ }
413
+ if (NULL != sel->alias) {
414
+ key = sel->alias;
415
+ }
416
+ if (NULL != sel->type && GQL_LIST == sel->type->kind) {
417
+ gqlValue list;
418
+ int cnt;
419
+ int i;
420
+
421
+ rb_check_type(child, RUBY_T_ARRAY);
422
+ if (NULL == (list = gql_list_create(err, NULL))) {
423
+ return err->code;
424
+ }
425
+ cnt = (int)RARRAY_LEN(child);
426
+ for (i = 0; i < cnt; i++) {
427
+ gqlValue co;
428
+
429
+ if (NULL != sel->type->base && GQL_SCALAR != sel->type->base->kind) {
430
+ struct _gqlField cf;
431
+
432
+ if (NULL == (co = gql_object_create(err)) ||
433
+ AGOO_ERR_OK != gql_list_append(err, list, co)) {
434
+ return err->code;
435
+ }
436
+ memset(&cf, 0, sizeof(cf));
437
+ cf.type = sel->type->base;
438
+
439
+ if (AGOO_ERR_OK != gql_eval_sels(err, doc, (gqlRef)rb_ary_entry(child, i), &cf, sel->sels, co, d2)) {
440
+ return err->code;
441
+ }
442
+ } else {
443
+ if (NULL == (co = coerce(err, (gqlRef)rb_ary_entry(child, i), sel->type->base)) ||
444
+ AGOO_ERR_OK != gql_list_append(err, list, co)) {
445
+ return err->code;
446
+ }
447
+ }
448
+ }
449
+ if (AGOO_ERR_OK != gql_object_set(err, result, key, list)) {
450
+ return err->code;
451
+ }
452
+ } else if (NULL == sel->sels) {
453
+ gqlValue cv;
454
+
455
+ if (NULL == (cv = coerce(err, (gqlRef)child, sel->type)) ||
456
+ AGOO_ERR_OK != gql_object_set(err, result, key, cv)) {
457
+ return err->code;
458
+ }
459
+ } else {
460
+ gqlValue co;
461
+
462
+ if (0 == depth) {
463
+ co = result;
464
+ } else if (NULL == (co = gql_object_create(err)) ||
465
+ AGOO_ERR_OK != gql_object_set(err, result, key, co)) {
466
+ return err->code;
467
+ }
468
+ if (AGOO_ERR_OK != gql_eval_sels(err, doc, (gqlRef)child, field, sel->sels, co, d2)) {
469
+ return err->code;
470
+ }
471
+ }
472
+ return AGOO_ERR_OK;
473
+ }
474
+
475
+ static void
476
+ ruby_types_cb(gqlType type, void *ctx) {
477
+ gqlDirUse dir;
478
+
479
+ for (dir = type->dir; NULL != dir; dir = dir->next) {
480
+ if (NULL != dir->dir && 0 == strcmp("ruby", dir->dir->name)) {
481
+ if (NULL != type_class_map) {
482
+ TypeClass tc = &type_class_map[*(int*)ctx];
483
+ gqlLink arg;
484
+
485
+ tc->type = type;
486
+ for (arg = dir->args; NULL != arg; arg = arg->next) {
487
+ if (0 == strcmp("class", arg->key)) {
488
+ tc->classname = gql_string_get(arg->value);
489
+ }
490
+ }
491
+ }
492
+ *((int*)ctx) += 1;
493
+ }
494
+ }
495
+ }
496
+
497
+ static int
498
+ build_type_class_map(agooErr err) {
499
+ int cnt = 0;
500
+
501
+ free(type_class_map);
502
+ type_class_map = NULL;
503
+
504
+ gql_type_iterate(ruby_types_cb, &cnt);
505
+
506
+ if (NULL == (type_class_map = (TypeClass)malloc(sizeof(struct _typeClass) * (cnt + 1)))) {
507
+ return agoo_err_set(err, AGOO_ERR_MEMORY, "out of memory");
508
+ }
509
+ memset(type_class_map, 0, sizeof(struct _typeClass) * (cnt + 1));
510
+ cnt = 0;
511
+ gql_type_iterate(ruby_types_cb, &cnt);
512
+
513
+ return AGOO_ERR_OK;
514
+ }
515
+
516
+ static gqlRef
517
+ root_op(const char *op) {
518
+ return (gqlRef)rb_funcall((VALUE)gql_root, rb_intern(op), 0);
519
+ }
520
+
521
+ static VALUE
522
+ rescue_yield_error(VALUE x) {
523
+ agooErr err = (agooErr)x;
524
+ volatile VALUE info = rb_errinfo();
525
+ volatile VALUE msg = rb_funcall(info, rb_intern("message"), 0);
526
+ const char *classname = rb_obj_classname(info);
527
+ const char *ms = rb_string_value_ptr(&msg);
528
+
529
+ agoo_err_set(err, AGOO_ERR_EVAL, "%s: %s", classname, ms);
530
+
531
+ return Qfalse;
532
+ }
533
+
534
+ static VALUE
535
+ rescue_yield(VALUE x) {
536
+ rb_yield_values2(0, NULL);
537
+
538
+ return Qnil;
539
+ }
540
+
541
+ /* Document-method: schema
542
+ *
543
+ * call-seq: schema(root) { }
544
+ *
545
+ * Start the GraphQL/Ruby integration by assigning a root Ruby object to the
546
+ * GraphQL environment. Within the block passed to the function SDL strings or
547
+ * files can be loaded. On exiting the block validation of the loaded schema
548
+ * is performed.
549
+ *
550
+ * Note that the _@ruby_ directive is added to the _schema_ type as well as
551
+ * the _Query_, _Mutation_, and _Subscription_ types based on the _root_
552
+ * class. Any _@ruby_ directives on the object types in the SDL will also
553
+ * associate a GraphQL and Ruby class. The association will be used to
554
+ * validate the Ruby class as a way to verify the class implements the methods
555
+ * described by the GraphQL type. The association is also use for resolving
556
+ * @skip and @include directives.
557
+ */
558
+ static VALUE
559
+ graphql_schema(VALUE self, VALUE root) {
560
+ struct _agooErr err = AGOO_ERR_INIT;
561
+ gqlType type;
562
+ gqlDir dir;
563
+ gqlDirUse use;
564
+
565
+ if (!rb_block_given_p()) {
566
+ rb_raise(rb_eStandardError, "A block is required.");
567
+ }
568
+ if (AGOO_ERR_OK != gql_init(&err)) {
569
+ printf("*-*-* %s\n", err.msg);
570
+ exit(0);
571
+ }
572
+ if (NULL == (dir = gql_directive_create(&err, "ruby", "Associates a Ruby class with a GraphQL type.", 0))) {
573
+ printf("*-*-* %s\n", err.msg);
574
+ exit(0);
575
+ }
576
+ if (NULL == gql_dir_arg(&err, dir, "class", &gql_string_type, NULL, 0, NULL, true)) {
577
+ printf("*-*-* %s\n", err.msg);
578
+ exit(0);
579
+ }
580
+ if (AGOO_ERR_OK != gql_directive_on(&err, dir, "SCHEMA", 6) ||
581
+ AGOO_ERR_OK != gql_directive_on(&err, dir, "OBJECT", 6)) {
582
+ printf("*-*-* %s\n", err.msg);
583
+ exit(0);
584
+ }
585
+ gql_root = (gqlRef)root;
586
+ rb_gc_register_address(&root);
587
+
588
+ gql_doc_eval_func = eval_wrap;
589
+ gql_resolve_func = resolve;
590
+ gql_type_func = ref_type;
591
+ gql_root_op = root_op;
592
+
593
+ if (NULL == (use = gql_dir_use_create(&err, "ruby"))) {
594
+ printf("*-*-* %s\n", err.msg);
595
+ exit(0);
596
+ }
597
+ if (AGOO_ERR_OK != gql_dir_use_arg(&err, use, "class", gql_string_create(&err, rb_obj_classname(root), 0))) {
598
+ printf("*-*-* %s\n", err.msg);
599
+ exit(0);
600
+ }
601
+ if (NULL == (type = gql_type_get("schema"))) {
602
+ printf("*-*-* Error: Failed to find the 'schema' type.");
603
+ exit(0);
604
+ }
605
+ gql_type_directive_use(type, use);
606
+
607
+ if (AGOO_ERR_OK != make_ruby_use(&err, root, "query", "Query") ||
608
+ AGOO_ERR_OK != make_ruby_use(&err, root, "mutation", "Mutation") ||
609
+ AGOO_ERR_OK != make_ruby_use(&err, root, "subscription", "Subscription")) {
610
+ printf("*-*-* %s\n", err.msg);
611
+ exit(0);
612
+ }
613
+ rb_rescue2(rescue_yield, Qnil, rescue_yield_error, (VALUE)&err, rb_eException, 0);
614
+ if (AGOO_ERR_OK != err.code) {
615
+ printf("*-*-* %s\n", err.msg);
616
+ exit(0);
617
+ }
618
+ if (AGOO_ERR_OK != gql_validate(&err) ||
619
+ AGOO_ERR_OK != build_type_class_map(&err)) {
620
+ printf("*-*-* %s\n", err.msg);
621
+ exit(0);
622
+ }
623
+ return Qnil;
624
+ }
625
+
626
+ /* Document-method: load
627
+ *
628
+ * call-seq: load(sdl)
629
+ *
630
+ * Load an SDL string. This should only be called in a block provided to a
631
+ * call to _#schema_.
632
+ */
633
+ static VALUE
634
+ graphql_load(VALUE self, VALUE sdl) {
635
+ struct _agooErr err = AGOO_ERR_INIT;
636
+
637
+ if (NULL == gql_root) {
638
+ rb_raise(rb_eStandardError, "GraphQL root not set. Use Agoo::GraphQL.schema.");
639
+ }
640
+ rb_check_type(sdl, T_STRING);
641
+ if (AGOO_ERR_OK != sdl_parse(&err, StringValuePtr(sdl), RSTRING_LEN(sdl))) {
642
+ rb_raise(rb_eStandardError, "%s", err.msg);
643
+ }
644
+ return Qnil;
645
+ }
646
+
647
+ /* Document-method: load_file
648
+ *
649
+ * call-seq: load_file(sdl_file)
650
+ *
651
+ * Load an SDL file. This should only be called in a block provided to a call
652
+ * to _#schema_.
653
+ */
654
+ static VALUE
655
+ graphql_load_file(VALUE self, VALUE path) {
656
+ struct _agooErr err = AGOO_ERR_INIT;
657
+ FILE *f;
658
+ size_t len;
659
+ char *sdl;
660
+
661
+ if (NULL == gql_root) {
662
+ rb_raise(rb_eStandardError, "GraphQL root not set. Use Agoo::GraphQL.schema.");
663
+ }
664
+ rb_check_type(path, T_STRING);
665
+ if (NULL == (f = fopen(StringValuePtr(path), "r"))) {
666
+ rb_raise(rb_eIOError, "%s", strerror(errno));
667
+ }
668
+ if (0 != fseek(f, 0, SEEK_END)) {
669
+ rb_raise(rb_eIOError, "%s", strerror(errno));
670
+ }
671
+ len = ftell(f);
672
+ sdl = ALLOC_N(char, len + 1);
673
+ fseek(f, 0, SEEK_SET);
674
+ if (len != fread(sdl, 1, len, f)) {
675
+ rb_raise(rb_eIOError, "%s", strerror(errno));
676
+ } else {
677
+ sdl[len] = '\0';
678
+ }
679
+ fclose(f);
680
+ if (AGOO_ERR_OK != sdl_parse(&err, sdl, len)) {
681
+ xfree(sdl);
682
+ rb_raise(rb_eStandardError, "%s", err.msg);
683
+ }
684
+ xfree(sdl);
685
+
686
+ return Qnil;
687
+ }
688
+
689
+ /* Document-method: dump_sdl
690
+ *
691
+ * call-seq: dump_sdl()
692
+ *
693
+ * The preferred method of inspecting a GraphQL schema is to use introspection
694
+ * queries but for debugging and for reviewing the schema a dump of the schema
695
+ * as SDL can be helpful. The _#dump_sdl_ method returns the schema as an SDL
696
+ * string.
697
+ *
698
+ * - *options* [_Hash_] server options
699
+ *
700
+ * - *:with_description* [_true_|_false_] if true the description strings are included. If false they are not included.
701
+ *
702
+ * - *:all* [_true_|_false_] if true all types and directives are included in the dump. If false only the user created types are include.
703
+ *
704
+ */
705
+ static VALUE
706
+ graphql_sdl_dump(VALUE self, VALUE options) {
707
+ agooText t = agoo_text_allocate(4096);
708
+ volatile VALUE dump;
709
+ VALUE v;
710
+ bool with_desc = true;
711
+ bool all = false;
712
+
713
+ Check_Type(options, T_HASH);
714
+
715
+ v = rb_hash_aref(options, ID2SYM(rb_intern("with_descriptions")));
716
+ if (Qnil != v) {
717
+ with_desc = (Qtrue == v);
718
+ }
719
+ v = rb_hash_aref(options, ID2SYM(rb_intern("all")));
720
+ if (Qnil != v) {
721
+ all = (Qtrue == v);
722
+ }
723
+ t = gql_schema_sdl(t, with_desc, all);
724
+
725
+ dump = rb_str_new(t->text, t->len);
726
+ agoo_text_release(t);
727
+
728
+ return dump;
729
+ }
730
+
731
+ /* Document-class: Agoo::Graphql
732
+ *
733
+ * The Agoo::GraphQL class provides support for the GraphQL API as defined in
734
+ * https://facebook.github.io/graphql/June2018. The approach taken supporting
735
+ * GraphQL with Ruby is to keep the integration as simple as possible. With
736
+ * that in mind there are not new languages or syntax to learn. GraphQL types
737
+ * are defined with SDL which is the language used in the specification. Ruby,
738
+ * is well, just Ruby. A GraphQL type is assigned a Ruby class that implements
739
+ * that type. Thats it. A GraphQL directive or a Ruby method is used to create
740
+ * this association. After that the Agoo server does the work and calls the
741
+ * Ruby object methods as needed to satisfy the GraphQL queries.
742
+ */
743
+ void
744
+ graphql_init(VALUE mod) {
745
+ graphql_class = rb_define_class_under(mod, "GraphQL", rb_cObject);
746
+
747
+ rb_define_module_function(graphql_class, "schema", graphql_schema, 1);
748
+
749
+ rb_define_module_function(graphql_class, "load", graphql_load, 1);
750
+ rb_define_module_function(graphql_class, "load_file", graphql_load_file, 1);
751
+
752
+ rb_define_module_function(graphql_class, "sdl_dump", graphql_sdl_dump, 1);
753
+ }