bluejay 0.1.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Cargo.lock +423 -0
- data/Cargo.toml +2 -0
- data/LICENSE +21 -0
- data/README.md +33 -0
- data/ext/Cargo.toml +17 -0
- data/ext/extconf.rb +6 -0
- data/ext/src/execution/coerce_result.rs +54 -0
- data/ext/src/execution/engine.rs +466 -0
- data/ext/src/execution/execution_error.rs +28 -0
- data/ext/src/execution/field_error.rs +8 -0
- data/ext/src/execution/key_store.rs +23 -0
- data/ext/src/execution.rs +10 -0
- data/ext/src/helpers/public_name.rs +21 -0
- data/ext/src/helpers/typed_frozen_r_array.rs +56 -0
- data/ext/src/helpers/wrapped_definition.rs +79 -0
- data/ext/src/helpers/wrapped_struct.rs +97 -0
- data/ext/src/helpers.rs +8 -0
- data/ext/src/lib.rs +10 -0
- data/ext/src/ruby_api/arguments_definition.rs +8 -0
- data/ext/src/ruby_api/coerce_input.rs +6 -0
- data/ext/src/ruby_api/coercion_error.rs +61 -0
- data/ext/src/ruby_api/custom_scalar_type_definition.rs +62 -0
- data/ext/src/ruby_api/enum_type_definition.rs +107 -0
- data/ext/src/ruby_api/enum_value_definition.rs +58 -0
- data/ext/src/ruby_api/enum_value_definitions.rs +8 -0
- data/ext/src/ruby_api/execution_error.rs +48 -0
- data/ext/src/ruby_api/execution_result.rs +40 -0
- data/ext/src/ruby_api/field_definition.rs +112 -0
- data/ext/src/ruby_api/fields_definition.rs +8 -0
- data/ext/src/ruby_api/input_fields_definition.rs +8 -0
- data/ext/src/ruby_api/input_object_type_definition.rs +138 -0
- data/ext/src/ruby_api/input_type_reference.rs +358 -0
- data/ext/src/ruby_api/input_value_definition.rs +98 -0
- data/ext/src/ruby_api/interface_implementation.rs +42 -0
- data/ext/src/ruby_api/interface_implementations.rs +8 -0
- data/ext/src/ruby_api/interface_type_definition.rs +82 -0
- data/ext/src/ruby_api/json_value.rs +111 -0
- data/ext/src/ruby_api/object_type_definition.rs +100 -0
- data/ext/src/ruby_api/output_type_reference.rs +238 -0
- data/ext/src/ruby_api/r_result.rs +84 -0
- data/ext/src/ruby_api/scalar.rs +45 -0
- data/ext/src/ruby_api/schema_definition.rs +270 -0
- data/ext/src/ruby_api/union_member_type.rs +41 -0
- data/ext/src/ruby_api/union_member_types.rs +8 -0
- data/ext/src/ruby_api/union_type_definition.rs +89 -0
- data/ext/src/ruby_api/validation_error.rs +63 -0
- data/ext/src/ruby_api.rs +75 -0
- data/lib/bluejay/base_input_type_reference.rb +13 -0
- data/lib/bluejay/base_output_type_reference.rb +15 -0
- data/lib/bluejay/custom_scalar_type.rb +40 -0
- data/lib/bluejay/enum_type.rb +44 -0
- data/lib/bluejay/ext.bundle +0 -0
- data/lib/bluejay/finalize.rb +27 -0
- data/lib/bluejay/input_type.rb +64 -0
- data/lib/bluejay/input_type_reference_shorthands.rb +28 -0
- data/lib/bluejay/interface_type.rb +60 -0
- data/lib/bluejay/json_value.rb +16 -0
- data/lib/bluejay/name_from_class.rb +18 -0
- data/lib/bluejay/object_type.rb +68 -0
- data/lib/bluejay/output_type_reference_shorthands.rb +28 -0
- data/lib/bluejay/schema.rb +63 -0
- data/lib/bluejay/union_type.rb +43 -0
- data/lib/bluejay/version.rb +5 -0
- data/lib/bluejay.rb +28 -0
- data/lib/rbi_ext/model.rb +64 -0
- data/lib/tapioca/dsl/compilers/input_type.rb +34 -0
- data/lib/tapioca/dsl/compilers/interface_type.rb +29 -0
- data/lib/tapioca/dsl/compilers/object_type.rb +43 -0
- data/lib/tapioca/dsl/compilers/schema.rb +42 -0
- 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,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,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
|
+
}
|