bluejay 0.1.0.alpha.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.
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
+ }