bluejay 0.1.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +423 -0
  3. data/Cargo.toml +2 -0
  4. data/LICENSE +21 -0
  5. data/README.md +33 -0
  6. data/ext/Cargo.toml +17 -0
  7. data/ext/extconf.rb +6 -0
  8. data/ext/src/execution/coerce_result.rs +54 -0
  9. data/ext/src/execution/engine.rs +466 -0
  10. data/ext/src/execution/execution_error.rs +28 -0
  11. data/ext/src/execution/field_error.rs +8 -0
  12. data/ext/src/execution/key_store.rs +23 -0
  13. data/ext/src/execution.rs +10 -0
  14. data/ext/src/helpers/public_name.rs +21 -0
  15. data/ext/src/helpers/typed_frozen_r_array.rs +56 -0
  16. data/ext/src/helpers/wrapped_definition.rs +79 -0
  17. data/ext/src/helpers/wrapped_struct.rs +97 -0
  18. data/ext/src/helpers.rs +8 -0
  19. data/ext/src/lib.rs +10 -0
  20. data/ext/src/ruby_api/arguments_definition.rs +8 -0
  21. data/ext/src/ruby_api/coerce_input.rs +6 -0
  22. data/ext/src/ruby_api/coercion_error.rs +61 -0
  23. data/ext/src/ruby_api/custom_scalar_type_definition.rs +62 -0
  24. data/ext/src/ruby_api/enum_type_definition.rs +107 -0
  25. data/ext/src/ruby_api/enum_value_definition.rs +58 -0
  26. data/ext/src/ruby_api/enum_value_definitions.rs +8 -0
  27. data/ext/src/ruby_api/execution_error.rs +48 -0
  28. data/ext/src/ruby_api/execution_result.rs +40 -0
  29. data/ext/src/ruby_api/field_definition.rs +112 -0
  30. data/ext/src/ruby_api/fields_definition.rs +8 -0
  31. data/ext/src/ruby_api/input_fields_definition.rs +8 -0
  32. data/ext/src/ruby_api/input_object_type_definition.rs +138 -0
  33. data/ext/src/ruby_api/input_type_reference.rs +358 -0
  34. data/ext/src/ruby_api/input_value_definition.rs +98 -0
  35. data/ext/src/ruby_api/interface_implementation.rs +42 -0
  36. data/ext/src/ruby_api/interface_implementations.rs +8 -0
  37. data/ext/src/ruby_api/interface_type_definition.rs +82 -0
  38. data/ext/src/ruby_api/json_value.rs +111 -0
  39. data/ext/src/ruby_api/object_type_definition.rs +100 -0
  40. data/ext/src/ruby_api/output_type_reference.rs +238 -0
  41. data/ext/src/ruby_api/r_result.rs +84 -0
  42. data/ext/src/ruby_api/scalar.rs +45 -0
  43. data/ext/src/ruby_api/schema_definition.rs +270 -0
  44. data/ext/src/ruby_api/union_member_type.rs +41 -0
  45. data/ext/src/ruby_api/union_member_types.rs +8 -0
  46. data/ext/src/ruby_api/union_type_definition.rs +89 -0
  47. data/ext/src/ruby_api/validation_error.rs +63 -0
  48. data/ext/src/ruby_api.rs +75 -0
  49. data/lib/bluejay/base_input_type_reference.rb +13 -0
  50. data/lib/bluejay/base_output_type_reference.rb +15 -0
  51. data/lib/bluejay/custom_scalar_type.rb +40 -0
  52. data/lib/bluejay/enum_type.rb +44 -0
  53. data/lib/bluejay/ext.bundle +0 -0
  54. data/lib/bluejay/finalize.rb +27 -0
  55. data/lib/bluejay/input_type.rb +64 -0
  56. data/lib/bluejay/input_type_reference_shorthands.rb +28 -0
  57. data/lib/bluejay/interface_type.rb +60 -0
  58. data/lib/bluejay/json_value.rb +16 -0
  59. data/lib/bluejay/name_from_class.rb +18 -0
  60. data/lib/bluejay/object_type.rb +68 -0
  61. data/lib/bluejay/output_type_reference_shorthands.rb +28 -0
  62. data/lib/bluejay/schema.rb +63 -0
  63. data/lib/bluejay/union_type.rb +43 -0
  64. data/lib/bluejay/version.rb +5 -0
  65. data/lib/bluejay.rb +28 -0
  66. data/lib/rbi_ext/model.rb +64 -0
  67. data/lib/tapioca/dsl/compilers/input_type.rb +34 -0
  68. data/lib/tapioca/dsl/compilers/interface_type.rb +29 -0
  69. data/lib/tapioca/dsl/compilers/object_type.rb +43 -0
  70. data/lib/tapioca/dsl/compilers/schema.rb +42 -0
  71. metadata +131 -0
@@ -0,0 +1,466 @@
1
+ use std::collections::{HashSet, BTreeMap};
2
+ use bluejay_core::{
3
+ AsIter,
4
+ Value as CoreValue,
5
+ BooleanValue,
6
+ IntegerValue,
7
+ FloatValue,
8
+ ObjectValue,
9
+ Variable as CoreVariable,
10
+ OperationType,
11
+ definition::{
12
+ OutputTypeReference as CoreOutputTypeReference,
13
+ },
14
+ };
15
+ use bluejay_parser::ast::executable::{
16
+ ExecutableDocument,
17
+ OperationDefinition,
18
+ VariableDefinition,
19
+ Selection,
20
+ Field,
21
+ };
22
+ use bluejay_parser::ast::{
23
+ VariableArgument,
24
+ VariableValue,
25
+ };
26
+ use crate::ruby_api::{
27
+ SchemaDefinition,
28
+ ExecutionError as RubyExecutionError,
29
+ TypeDefinitionReference,
30
+ BaseInputTypeReference,
31
+ InputTypeReference,
32
+ CoerceInput,
33
+ ObjectTypeDefinition,
34
+ ExecutionResult,
35
+ OutputTypeReference,
36
+ BaseOutputTypeReference,
37
+ FieldDefinition,
38
+ InterfaceTypeDefinition,
39
+ UnionTypeDefinition,
40
+ };
41
+ use crate::execution::{
42
+ ExecutionError,
43
+ FieldError,
44
+ CoerceResult,
45
+ KeyStore,
46
+ };
47
+ use crate::helpers::WrappedStruct;
48
+ use magnus::{RHash, Value, QNIL, RArray, RString, Error};
49
+
50
+ pub struct Engine<'a> {
51
+ schema: &'a SchemaDefinition,
52
+ document: &'a ExecutableDocument<'a>,
53
+ variable_values: &'a RHash, // pointer to ensure it stays on the stack somewhere
54
+ key_store: KeyStore<'a>,
55
+ }
56
+
57
+ impl<'a> Engine<'a> {
58
+ pub fn execute_request(schema: &SchemaDefinition, query: &str, operation_name: Option<&str>, variable_values: RHash, initial_value: Value) -> Result<ExecutionResult, Error> {
59
+ let (document, parse_errors) = bluejay_parser::ast::parse(query);
60
+
61
+ if !parse_errors.is_empty() {
62
+ return Ok(Self::execution_result(Default::default(), parse_errors.into_iter().map(ExecutionError::ParseError).collect()));
63
+ }
64
+
65
+ let operation_definition = match Self::get_operation(&document, operation_name) {
66
+ Ok(od) => od,
67
+ Err(error) => { return Ok(Self::execution_result(Default::default(), vec![error])); },
68
+ };
69
+
70
+ let coerced_variable_values = match Self::get_variable_values(&schema, &operation_definition, variable_values) {
71
+ Ok(cvv) => cvv,
72
+ Err(errors) => { return Ok(Self::execution_result(Default::default(), errors)); },
73
+ };
74
+
75
+ let instance = Engine { schema: &schema, document: &document, variable_values: &coerced_variable_values, key_store: KeyStore::new() };
76
+
77
+ match operation_definition.operation_type() {
78
+ OperationType::Query => {
79
+ let query_root = initial_value.funcall("query", ())?;
80
+ Ok(instance.execute_query(operation_definition, query_root))
81
+ },
82
+ OperationType::Mutation => unimplemented!("executing mutations has not been implemented yet"),
83
+ OperationType::Subscription => unreachable!(),
84
+ }
85
+ }
86
+
87
+ fn get_operation<'b>(document: &'b ExecutableDocument, operation_name: Option<&'b str>) -> Result<&'b OperationDefinition<'b>, ExecutionError<'b>> {
88
+ if let Some(operation_name) = operation_name {
89
+ document
90
+ .operation_definitions()
91
+ .iter()
92
+ .find(|od| matches!(od.name(), Some(n) if n == operation_name))
93
+ .ok_or_else(|| ExecutionError::NoOperationWithName { name: operation_name })
94
+ } else {
95
+ if document.operation_definitions().len() == 1 {
96
+ Ok(&document.operation_definitions()[0])
97
+ } else {
98
+ Err(ExecutionError::CannotUseAnonymousOperation)
99
+ }
100
+ }
101
+ }
102
+
103
+ fn get_variable_values<'b>(schema: &'b SchemaDefinition, operation: &'b OperationDefinition, variable_values: RHash) -> Result<RHash, Vec<ExecutionError<'b>>> {
104
+ let coerced_values = RHash::new();
105
+ let variable_definitions: &[VariableDefinition] = operation.variable_definitions().map(AsRef::as_ref).unwrap_or_default();
106
+ let mut errors: Vec<ExecutionError<'b>> = Vec::new();
107
+
108
+ for variable_definition in variable_definitions {
109
+ let variable_name = variable_definition.variable().name();
110
+ let variable_named_type_reference = variable_definition.r#type();
111
+ let variable_base_type = schema.r#type(variable_named_type_reference.name()).unwrap();
112
+ let base_input_type_reference: BaseInputTypeReference = variable_base_type.try_into().unwrap();
113
+ let variable_type = InputTypeReference::from_parser_type_reference(variable_named_type_reference, base_input_type_reference);
114
+ let default_value = variable_definition.default_value();
115
+ let value = variable_values.get(variable_name);
116
+ let has_value = value.is_some();
117
+ if !has_value && default_value.is_some() {
118
+ coerced_values.aset(variable_name, Self::value_from_core_const_value(default_value.unwrap())).unwrap();
119
+ } else if variable_type.is_required() && !has_value {
120
+ errors.push(ExecutionError::RequiredVariableMissingValue { name: variable_name });
121
+ } else {
122
+ let path = vec![variable_name.to_owned()];
123
+ let value = value.unwrap_or_default();
124
+ match variable_type.coerce_input(value, &path) {
125
+ Ok(Ok(coerced_value)) => { coerced_values.aset(variable_name, coerced_value).unwrap(); },
126
+ Ok(Err(coercion_errors)) => {
127
+ errors.extend(coercion_errors.into_iter().map(|ce| ExecutionError::CoercionError(ce)));
128
+ },
129
+ Err(error) => errors.push(ExecutionError::ApplicationError(error)),
130
+ }
131
+ }
132
+ }
133
+
134
+ if errors.is_empty() {
135
+ Ok(coerced_values)
136
+ } else {
137
+ Err(errors)
138
+ }
139
+ }
140
+
141
+ fn value_from_core_const_value(value: &impl bluejay_core::AbstractConstValue) -> Value {
142
+ match value.as_ref() {
143
+ CoreValue::Boolean(b) => b.to_bool().into(),
144
+ CoreValue::Enum(e) => e.as_ref().into(),
145
+ CoreValue::Float(f) => f.to_f64().into(),
146
+ CoreValue::Integer(i) => i.to_i32().into(),
147
+ CoreValue::Null(_) => *QNIL,
148
+ CoreValue::String(s) => s.as_ref().into(),
149
+ CoreValue::Variable(_) => unreachable!(),
150
+ CoreValue::List(l) => {
151
+ *RArray::from_iter(l.as_ref().iter().map(Self::value_from_core_const_value))
152
+ },
153
+ CoreValue::Object(o) => {
154
+ *RHash::from_iter(o.fields().iter().map(|(k, v)| (k.as_ref(), Self::value_from_core_const_value(v))))
155
+ },
156
+ }
157
+ }
158
+
159
+ fn value_from_core_variable_value(value: &impl bluejay_core::AbstractVariableValue, variable_values: RHash) -> Value {
160
+ match value.as_ref() {
161
+ CoreValue::Boolean(b) => b.to_bool().into(),
162
+ CoreValue::Enum(e) => e.as_ref().into(),
163
+ CoreValue::Float(f) => f.to_f64().into(),
164
+ CoreValue::Integer(i) => i.to_i32().into(),
165
+ CoreValue::Null(_) => *QNIL,
166
+ CoreValue::String(s) => s.as_ref().into(),
167
+ CoreValue::Variable(v) => {
168
+ variable_values.get(v.name()).unwrap_or(*QNIL)
169
+ },
170
+ CoreValue::List(l) => {
171
+ *RArray::from_iter(l.as_ref().iter().map(|v| Self::value_from_core_variable_value(v, variable_values)))
172
+ },
173
+ CoreValue::Object(o) => {
174
+ *RHash::from_iter(o.fields().iter().map(|(k, v)| (k.as_ref(), Self::value_from_core_variable_value(v, variable_values))))
175
+ },
176
+ }
177
+ }
178
+
179
+ fn execution_result(value: Value, errors: Vec<ExecutionError>) -> ExecutionResult {
180
+ let errors: Vec<WrappedStruct<RubyExecutionError>> = errors.into_iter().map(|error| WrappedStruct::wrap(error.into())).collect();
181
+ ExecutionResult::new(value, errors)
182
+ }
183
+
184
+ fn execute_query(&'a self, query: &'a OperationDefinition, initial_value: Value) -> ExecutionResult {
185
+ let query_type = self.schema.query();
186
+ let query_type = query_type.get();
187
+ let selection_set = query.selection_set();
188
+
189
+ let (value, errors) = self.execute_selection_set(selection_set.as_ref().iter(), query_type, initial_value);
190
+
191
+ Self::execution_result(value, errors)
192
+ }
193
+
194
+ fn execute_selection_set(&'a self, selection_set: impl Iterator<Item=&'a Selection<'a>>, object_type: &ObjectTypeDefinition, object_value: Value) -> (Value, Vec<ExecutionError<'a>>) {
195
+ let mut visited_fragments = HashSet::new();
196
+ let grouped_field_set = self.collect_fields(object_type, selection_set, &mut visited_fragments);
197
+
198
+ let result_map = RHash::new();
199
+ let mut errors = Vec::new();
200
+ let mut has_null_for_required = false;
201
+
202
+ for (response_key, fields) in grouped_field_set {
203
+ let field_name = fields.first().unwrap().name();
204
+ let field_definition = object_type.field_definition(field_name).unwrap();
205
+ let (response_value, mut errs) = self.execute_field(object_type, object_value, field_definition, &fields);
206
+ if field_definition.r#type().as_ref().is_required() && response_value.is_nil() {
207
+ has_null_for_required = true;
208
+ }
209
+ let key: RString = self.key_store.get(response_key);
210
+ result_map.aset(key, response_value).unwrap();
211
+ errors.append(&mut errs);
212
+ }
213
+
214
+ if has_null_for_required {
215
+ (*QNIL, errors)
216
+ } else {
217
+ (*result_map, errors)
218
+ }
219
+ }
220
+
221
+ fn collect_fields(&'a self, object_type: &ObjectTypeDefinition, selection_set: impl Iterator<Item=&'a Selection<'a>>, visited_fragments: &mut HashSet<&'a str>) -> BTreeMap<&'a str, Vec<&'a Field>> {
222
+ let mut grouped_fields: BTreeMap<&'a str, Vec<&'a Field>> = BTreeMap::new();
223
+
224
+ for selection in selection_set {
225
+ // TODO: skip directive check
226
+ // TODO: include directive check
227
+ match selection {
228
+ Selection::Field(field) => {
229
+ let response_key = field.response_key();
230
+ let entry_for_response_key = grouped_fields.entry(response_key).or_default();
231
+ entry_for_response_key.push(field);
232
+ },
233
+ Selection::FragmentSpread(fragment_spread) => {
234
+ let fragment_spread_name = fragment_spread.name();
235
+ if visited_fragments.insert(fragment_spread_name) {
236
+ let fragment = self.document
237
+ .fragment_definitions()
238
+ .iter()
239
+ .find(|fd| fd.name() == fragment_spread_name);
240
+
241
+ let fragment = match fragment {
242
+ Some(f) => f,
243
+ None => { continue; },
244
+ };
245
+
246
+ let fragment_type = fragment.type_condition();
247
+
248
+ if !self.does_fragment_type_apply(object_type, fragment_type) {
249
+ continue;
250
+ }
251
+
252
+ let fragment_selection_set = fragment.selection_set();
253
+
254
+ let fragment_grouped_field_set = self.collect_fields(object_type, fragment_selection_set.as_ref().iter(), visited_fragments);
255
+
256
+ for (response_key, fragment_group) in &fragment_grouped_field_set {
257
+ let group_for_response_key = grouped_fields.entry(response_key).or_default();
258
+ group_for_response_key.extend_from_slice(&fragment_group);
259
+ }
260
+ }
261
+ },
262
+ Selection::InlineFragment(inline_fragment) => {
263
+ let fragment_type = inline_fragment.type_condition();
264
+
265
+ if matches!(fragment_type, Some(fragment_type) if !self.does_fragment_type_apply(object_type, fragment_type)) {
266
+ continue;
267
+ }
268
+
269
+ let fragment_selection_set = inline_fragment.selection_set();
270
+
271
+ let fragment_grouped_field_set = self.collect_fields(object_type, fragment_selection_set.as_ref().iter(), visited_fragments);
272
+
273
+ for (response_key, fragment_group) in &fragment_grouped_field_set {
274
+ let group_for_response_key = grouped_fields.entry(response_key).or_default();
275
+ group_for_response_key.extend_from_slice(&fragment_group);
276
+ }
277
+ },
278
+ }
279
+ }
280
+
281
+ grouped_fields
282
+ }
283
+
284
+ fn does_fragment_type_apply(&'a self, object_type: &ObjectTypeDefinition, fragment_type_name: &str) -> bool {
285
+ let fragment_type = self.schema.r#type(fragment_type_name).unwrap();
286
+
287
+ match fragment_type {
288
+ TypeDefinitionReference::ObjectType(otd, _) => {
289
+ // TODO: see if there's any edge case where name equality does not work
290
+ otd.as_ref().name() == object_type.name()
291
+ },
292
+ TypeDefinitionReference::InterfaceType(itd, _) => {
293
+ object_type.implements_interface(itd.as_ref())
294
+ },
295
+ TypeDefinitionReference::UnionType(utd, _) => {
296
+ utd.as_ref().contains_type(object_type)
297
+ },
298
+ TypeDefinitionReference::BuiltinScalarType(_) | TypeDefinitionReference::CustomScalarType(_, _) | TypeDefinitionReference::EnumType(_, _) | TypeDefinitionReference::InputObjectType(_, _) => unreachable!(),
299
+ }
300
+ }
301
+
302
+ fn execute_field(&'a self, object_type: &ObjectTypeDefinition, object_value: Value, field_definition: &FieldDefinition, fields: &[&'a Field]) -> (Value, Vec<ExecutionError<'a>>) {
303
+ let field = fields.first().unwrap();
304
+ let argument_values = match self.coerce_argument_values(field_definition, field) {
305
+ Ok(argument_values) => argument_values,
306
+ Err(errors) => { return (*QNIL, errors); },
307
+ };
308
+
309
+ let resolved_value = match self.resolve_field_value(object_type, object_value, field_definition, argument_values) {
310
+ Ok(resolved_value) => resolved_value,
311
+ Err(error) => { return (*QNIL, vec![error]); },
312
+ };
313
+
314
+ self.complete_value(field_definition.r#type(), fields, resolved_value)
315
+ }
316
+
317
+ fn coerce_argument_values(&'a self, field_definition: &FieldDefinition, field: &Field) -> Result<Vec<Value>, Vec<ExecutionError<'a>>> {
318
+ let mut coerced_args: Vec<Value> = Vec::new();
319
+ let mut errors: Vec<ExecutionError<'a>> = Vec::new();
320
+ let arguments: &[VariableArgument] = field.arguments().map(AsRef::as_ref).unwrap_or_default();
321
+ let argument_definitions = field_definition.argument_definitions();
322
+ for argument_definition in argument_definitions.iter() {
323
+ let argument_name = argument_definition.name();
324
+ let argument_type = argument_definition.r#type();
325
+ let default_value = argument_definition.default_value();
326
+ let argument_value: Option<Value> = arguments.iter().find(|argument| argument.name() == argument_name).and_then(|argument| {
327
+ let value = argument.value();
328
+ match value {
329
+ VariableValue::Variable(variable) => {
330
+ let variable_name = variable.name();
331
+ self.variable_values.get(variable_name)
332
+ },
333
+ _ => Some(Self::value_from_core_variable_value(value, *self.variable_values)),
334
+ }
335
+ });
336
+ let has_value = argument_value.is_some();
337
+ if !has_value && default_value.is_some() {
338
+ match argument_type.coerce_input(default_value.unwrap(), &[argument_name.to_owned()]) {
339
+ Ok(Ok(coerced_value)) => { coerced_args.push(coerced_value); },
340
+ // TODO: would be kind of bad if default value didn't coerce
341
+ Ok(Err(coercion_errors)) => { errors.extend(coercion_errors.into_iter().map(|ce| ExecutionError::CoercionError(ce))); },
342
+ Err(error) => { errors.push(ExecutionError::ApplicationError(error)); },
343
+ }
344
+ } else if argument_type.is_required() && (!has_value || matches!(argument_value, Some(v) if v.is_nil())) {
345
+ // TODO: field error
346
+ // shouldn't this never happen if query is validated and variables coerced to match definition in query?
347
+ } else {
348
+ // TODO: see if it is possible to distinguish between null and no value being passed
349
+ match argument_type.coerce_input(argument_value.unwrap_or_default(), &[argument_name.to_owned()]) {
350
+ Ok(Ok(coerced_value)) => { coerced_args.push(coerced_value); },
351
+ Ok(Err(coercion_errors)) => { errors.extend(coercion_errors.into_iter().map(|ce| ExecutionError::CoercionError(ce))); },
352
+ Err(error) => { errors.push(ExecutionError::ApplicationError(error)); },
353
+ }
354
+ }
355
+ }
356
+
357
+ if errors.is_empty() {
358
+ Ok(coerced_args)
359
+ } else {
360
+ Err(errors)
361
+ }
362
+ }
363
+
364
+ fn resolve_field_value(&'a self, _object_type: &ObjectTypeDefinition, object_value: Value, field_definition: & FieldDefinition, argument_values: Vec<Value>) -> Result<Value, ExecutionError<'a>> {
365
+ // TODO: use object_type somehow?
366
+ object_value.funcall(field_definition.ruby_resolver_method_name(), argument_values.as_slice())
367
+ .map_err(|error| ExecutionError::ApplicationError(error))
368
+ }
369
+
370
+ fn complete_value(&'a self, field_type: &OutputTypeReference, fields: &[&'a Field], result: Value) -> (Value, Vec<ExecutionError<'a>>) {
371
+ if field_type.as_ref().is_required() && result.is_nil() {
372
+ return (*QNIL, vec![ExecutionError::FieldError(FieldError::ReturnedNullForNonNullType)])
373
+ } else if result.is_nil() {
374
+ return (result, vec![])
375
+ }
376
+
377
+ match field_type.as_ref() {
378
+ CoreOutputTypeReference::Base(inner, _) => {
379
+ match inner {
380
+ BaseOutputTypeReference::BuiltinScalarType(bstd) => {
381
+ match bstd.coerce_result(result) {
382
+ Ok(value) => (value, vec![]),
383
+ Err(error) => (*QNIL, vec![ExecutionError::FieldError(error)]),
384
+ }
385
+ },
386
+ BaseOutputTypeReference::CustomScalarType(_) => (result, vec![]), // TODO: see if any checks are needed here
387
+ BaseOutputTypeReference::EnumType(etd) => {
388
+ match etd.as_ref().coerce_result(result) {
389
+ Ok(value) => (value, vec![]),
390
+ Err(error) => (*QNIL, vec![ExecutionError::FieldError(error)]),
391
+ }
392
+ },
393
+ BaseOutputTypeReference::ObjectType(otd) => {
394
+ let sub_selection_set = Self::merge_selection_sets(fields);
395
+ self.execute_selection_set(sub_selection_set, otd.as_ref(), result)
396
+ },
397
+ BaseOutputTypeReference::InterfaceType(itd) => {
398
+ let object_type = self.resolve_interface_type(itd.as_ref(), result);
399
+ let sub_selection_set = Self::merge_selection_sets(fields);
400
+ self.execute_selection_set(sub_selection_set, object_type, result)
401
+ },
402
+ BaseOutputTypeReference::UnionType(utd) => {
403
+ let object_type = self.resolve_union_type(utd.as_ref(), result);
404
+ let sub_selection_set = Self::merge_selection_sets(fields);
405
+ self.execute_selection_set(sub_selection_set, object_type, result)
406
+ },
407
+ }
408
+ },
409
+ CoreOutputTypeReference::List(inner, _) => {
410
+ if let Some(arr) = RArray::from_value(result) {
411
+ let inner = inner.get();
412
+ let completed = RArray::with_capacity(arr.len());
413
+ let mut errors: Vec<ExecutionError<'a>> = Vec::new();
414
+ let mut has_null = false;
415
+ for item in unsafe { arr.as_slice() } {
416
+ let (value, mut errs) = self.complete_value(inner, fields, *item);
417
+ completed.push(value).unwrap(); // TODO: make sure unwrapping is ok here
418
+ errors.append(&mut errs);
419
+ if value.is_nil() { has_null = true; }
420
+ }
421
+ if inner.as_ref().is_required() && has_null {
422
+ (*QNIL, errors)
423
+ } else {
424
+ (*completed, errors)
425
+ }
426
+ } else {
427
+ (*QNIL, vec![ExecutionError::FieldError(FieldError::ReturnedNonListForListType)])
428
+ }
429
+ },
430
+ }
431
+ }
432
+
433
+ fn merge_selection_sets<'b>(fields: &'b [&'a Field]) -> std::iter::FlatMap<std::slice::Iter<'b, &'a Field<'a>>, &'a [Selection<'a>], fn(&&'a Field) -> &'a [Selection<'a>]> {
434
+ fields
435
+ .iter()
436
+ .flat_map(|field| field.selection_set().map(AsRef::as_ref).unwrap_or_default())
437
+ }
438
+
439
+ fn resolve_interface_type(&'a self, interface_type: &InterfaceTypeDefinition, object_value: Value) -> &'a ObjectTypeDefinition {
440
+ // TODO: change to return Result<_, FieldError>
441
+ let typename: String = object_value.funcall(FieldDefinition::typename().get().ruby_resolver_method_name(), ()).unwrap();
442
+ let object_type = match self.schema.r#type(typename.as_str()) {
443
+ Some(TypeDefinitionReference::ObjectType(otd, _)) => otd.as_ref(),
444
+ _ => panic!(),
445
+ };
446
+ if object_type.implements_interface(interface_type) {
447
+ object_type
448
+ } else {
449
+ panic!()
450
+ }
451
+ }
452
+
453
+ fn resolve_union_type(&'a self, union_type: &UnionTypeDefinition, object_value: Value) -> &'a ObjectTypeDefinition {
454
+ // TODO: change to return Result<_, FieldError>
455
+ let typename: String = object_value.funcall(FieldDefinition::typename().get().ruby_resolver_method_name(), ()).unwrap();
456
+ let object_type = match self.schema.r#type(typename.as_str()) {
457
+ Some(TypeDefinitionReference::ObjectType(otd, _)) => otd.as_ref(),
458
+ _ => panic!(),
459
+ };
460
+ if union_type.contains_type(object_type) {
461
+ object_type
462
+ } else {
463
+ panic!()
464
+ }
465
+ }
466
+ }
@@ -0,0 +1,28 @@
1
+ use crate::ruby_api::{ExecutionError as RubyExecutionError, CoercionError};
2
+ use crate::execution::FieldError;
3
+ use magnus::{Error as MagnusError};
4
+ use bluejay_parser::{Error as ParseError};
5
+
6
+ pub enum ExecutionError<'a> {
7
+ NoOperationWithName { name: &'a str },
8
+ CannotUseAnonymousOperation,
9
+ RequiredVariableMissingValue { name: &'a str },
10
+ ApplicationError(MagnusError),
11
+ CoercionError(CoercionError),
12
+ ParseError(ParseError),
13
+ FieldError(FieldError),
14
+ }
15
+
16
+ impl<'a> Into<RubyExecutionError> for ExecutionError<'a> {
17
+ fn into(self) -> RubyExecutionError {
18
+ match self {
19
+ Self::NoOperationWithName { name } => RubyExecutionError::new(format!("No operation definition named `{}`", name)),
20
+ Self::CannotUseAnonymousOperation => RubyExecutionError::new("Operation name is required when document does not contain exactly 1 operation definition".to_string()),
21
+ Self::RequiredVariableMissingValue { name } => RubyExecutionError::new(format!("No value was provided for required variable `${}`", name)),
22
+ Self::ApplicationError(error) => RubyExecutionError::new(format!("Internal error: {}", error)),
23
+ Self::CoercionError(error) => error.into(),
24
+ Self::ParseError(error) => RubyExecutionError::new(error.message),
25
+ Self::FieldError(_) => RubyExecutionError::new("Field error".to_string()),
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,8 @@
1
+ use bluejay_core::BuiltinScalarDefinition;
2
+
3
+ pub enum FieldError {
4
+ ReturnedNullForNonNullType,
5
+ ReturnedNonListForListType,
6
+ CannotCoerceResultToBuiltinScalar { builtin_scalar: BuiltinScalarDefinition },
7
+ CannotCoerceResultToEnumType,
8
+ }
@@ -0,0 +1,23 @@
1
+ use std::collections::HashMap;
2
+ use std::cell::RefCell;
3
+ use magnus::{RString, RArray};
4
+
5
+ pub(super) struct KeyStore<'a> {
6
+ hash_map: RefCell<HashMap<&'a str, RString>>,
7
+ strings: RArray,
8
+ }
9
+
10
+ impl<'a> KeyStore<'a> {
11
+ pub fn new() -> Self {
12
+ Self { hash_map: RefCell::new(HashMap::new()), strings: RArray::new() }
13
+ }
14
+
15
+ pub fn get(&self, s: &'a str) -> RString {
16
+ *self.hash_map.borrow_mut().entry(s).or_insert_with(|| {
17
+ let s: RString = s.into();
18
+ s.freeze();
19
+ self.strings.push(s).unwrap();
20
+ s
21
+ })
22
+ }
23
+ }
@@ -0,0 +1,10 @@
1
+ mod coerce_result;
2
+ mod execution_error;
3
+ mod engine;
4
+ mod field_error;
5
+ mod key_store;
6
+ pub use coerce_result::CoerceResult;
7
+ use execution_error::ExecutionError;
8
+ pub use engine::Engine;
9
+ pub use field_error::FieldError;
10
+ use key_store::KeyStore;
@@ -0,0 +1,21 @@
1
+ use magnus::Value;
2
+
3
+ pub fn public_name(value: Value) -> &'static str {
4
+ if value.is_nil() {
5
+ "null"
6
+ } else if value.is_kind_of(magnus::class::integer()) {
7
+ "integer"
8
+ } else if value.is_kind_of(magnus::class::float()) {
9
+ "float"
10
+ } else if value.is_kind_of(magnus::class::string()) {
11
+ "string"
12
+ } else if value.is_kind_of(magnus::class::array()) {
13
+ "list"
14
+ } else if value.is_kind_of(magnus::class::hash()) {
15
+ "object"
16
+ } else if value.is_kind_of(magnus::class::true_class()) || value.is_kind_of(magnus::class::false_class()) {
17
+ "boolean"
18
+ } else {
19
+ "unknown"
20
+ }
21
+ }
@@ -0,0 +1,56 @@
1
+ use std::{marker::PhantomData, ops::Deref};
2
+ use magnus::{RArray, TryConvert, Error, TypedData, Value};
3
+ use crate::helpers::WrappedStruct;
4
+ use bluejay_core::AsIter;
5
+
6
+ #[derive(Debug)]
7
+ #[repr(transparent)]
8
+ pub struct TypedFrozenRArray<T: TryConvert> {
9
+ data: RArray,
10
+ t: PhantomData<T>,
11
+ }
12
+
13
+ impl<T: TryConvert> Clone for TypedFrozenRArray<T> {
14
+ fn clone(&self) -> Self {
15
+ Self { data: self.data, t: Default::default() }
16
+ }
17
+ }
18
+
19
+ impl<T: TryConvert> Copy for TypedFrozenRArray<T> {}
20
+
21
+ impl<T: TryConvert> TypedFrozenRArray<T> {
22
+ pub fn new(data: RArray) -> Result<Self, Error> {
23
+ data.freeze();
24
+ unsafe { data.as_slice() }.iter().try_for_each(|el| el.try_convert().map(|_: T| ()))?;
25
+ Ok(Self { data, t: Default::default() })
26
+ }
27
+
28
+ pub fn empty() -> Self {
29
+ let data = RArray::new();
30
+ data.freeze();
31
+ Self { data, t: Default::default() }
32
+ }
33
+ }
34
+
35
+ impl<T: TryConvert> Deref for TypedFrozenRArray<T> {
36
+ type Target = Value;
37
+
38
+ fn deref(&self) -> &Self::Target {
39
+ &*self.data
40
+ }
41
+ }
42
+
43
+ impl<T: TryConvert> Into<RArray> for TypedFrozenRArray<T> {
44
+ fn into(self) -> RArray {
45
+ self.data
46
+ }
47
+ }
48
+
49
+ impl<T: TypedData> AsIter for TypedFrozenRArray<WrappedStruct<T>> {
50
+ type Item = T;
51
+ type Iterator<'a> = std::iter::Map<std::slice::Iter<'a, Value>, fn(&'a Value) -> &'a T> where T: 'a;
52
+
53
+ fn iter<'a>(&'a self) -> Self::Iterator<'a> {
54
+ unsafe { self.data.as_slice() }.iter().map(|val| val.try_convert().unwrap())
55
+ }
56
+ }