1use crate::ast::{
7 Document, Field, OperationDefinition, OperationType, Selection, SelectionSet, Value,
8};
9use crate::request_deduplication::RequestDeduplicator;
10use crate::types::{GraphQLType, Schema};
11use anyhow::{anyhow, Result};
12use async_trait::async_trait;
13use serde_json::Value as JsonValue;
14use std::collections::HashMap;
15use std::sync::Arc;
16use tokio::sync::RwLock;
17
18#[derive(Debug, Clone)]
20pub struct ExecutionContext {
21 pub variables: HashMap<String, Value>,
22 pub operation_name: Option<String>,
23 pub request_id: String,
24 pub fragments: HashMap<String, crate::ast::FragmentDefinition>,
25}
26
27impl ExecutionContext {
28 pub fn new() -> Self {
29 Self {
30 variables: HashMap::new(),
31 operation_name: None,
32 request_id: uuid::Uuid::new_v4().to_string(),
33 fragments: HashMap::new(),
34 }
35 }
36
37 pub fn with_variables(mut self, variables: HashMap<String, Value>) -> Self {
38 self.variables = variables;
39 self
40 }
41
42 pub fn with_operation_name(mut self, operation_name: String) -> Self {
43 self.operation_name = Some(operation_name);
44 self
45 }
46
47 pub fn with_fragments(
48 mut self,
49 fragments: HashMap<String, crate::ast::FragmentDefinition>,
50 ) -> Self {
51 self.fragments = fragments;
52 self
53 }
54
55 pub fn add_fragment(&mut self, name: String, fragment: crate::ast::FragmentDefinition) {
56 self.fragments.insert(name, fragment);
57 }
58}
59
60impl Default for ExecutionContext {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66#[derive(Debug, Clone)]
68pub struct FragmentContext {
69 pub parent_type: String,
70 pub object_type: Option<String>,
71}
72
73impl FragmentContext {
74 pub fn new(parent_type: String) -> Self {
75 Self {
76 parent_type,
77 object_type: None,
78 }
79 }
80
81 pub fn with_object_type(mut self, object_type: String) -> Self {
82 self.object_type = Some(object_type);
83 self
84 }
85
86 pub fn can_apply_fragment(&self, type_condition: &str) -> bool {
87 if let Some(ref obj_type) = self.object_type {
89 obj_type == type_condition || self.parent_type == type_condition
90 } else {
91 self.parent_type == type_condition
92 }
93 }
94}
95
96#[async_trait]
98pub trait FieldResolver: Send + Sync {
99 async fn resolve_field(
100 &self,
101 field_name: &str,
102 args: &HashMap<String, Value>,
103 context: &ExecutionContext,
104 ) -> Result<Value>;
105}
106
107#[derive(Debug, Clone)]
109pub struct ExecutionResult {
110 pub data: Option<JsonValue>,
111 pub errors: Vec<GraphQLError>,
112}
113
114impl ExecutionResult {
115 pub fn new() -> Self {
116 Self {
117 data: None,
118 errors: Vec::new(),
119 }
120 }
121
122 pub fn with_data(mut self, data: JsonValue) -> Self {
123 self.data = Some(data);
124 self
125 }
126
127 pub fn with_error(mut self, error: GraphQLError) -> Self {
128 self.errors.push(error);
129 self
130 }
131
132 pub fn has_errors(&self) -> bool {
133 !self.errors.is_empty()
134 }
135}
136
137impl Default for ExecutionResult {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143#[derive(Debug, Clone, serde::Serialize)]
145pub struct GraphQLError {
146 pub message: String,
147 pub path: Vec<String>,
148 pub locations: Vec<SourceLocation>,
149 pub extensions: HashMap<String, JsonValue>,
150}
151
152impl GraphQLError {
153 pub fn new(message: String) -> Self {
154 Self {
155 message,
156 path: Vec::new(),
157 locations: Vec::new(),
158 extensions: HashMap::new(),
159 }
160 }
161
162 pub fn with_path(mut self, path: Vec<String>) -> Self {
163 self.path = path;
164 self
165 }
166
167 pub fn with_location(mut self, location: SourceLocation) -> Self {
168 self.locations.push(location);
169 self
170 }
171
172 pub fn with_extension(mut self, key: String, value: JsonValue) -> Self {
173 self.extensions.insert(key, value);
174 self
175 }
176}
177
178#[derive(Debug, Clone, serde::Serialize)]
180pub struct SourceLocation {
181 pub line: usize,
182 pub column: usize,
183}
184
185impl SourceLocation {
186 pub fn new(line: usize, column: usize) -> Self {
187 Self { line, column }
188 }
189}
190
191pub struct QueryExecutor {
193 schema: Arc<RwLock<Schema>>,
194 resolvers: HashMap<String, Arc<dyn FieldResolver>>,
195 deduplicator: Arc<RequestDeduplicator>,
196 eager_types: std::collections::HashSet<String>,
216}
217
218impl QueryExecutor {
219 pub fn new(schema: Schema) -> Self {
220 Self {
221 schema: Arc::new(RwLock::new(schema)),
222 resolvers: HashMap::new(),
223 deduplicator: Arc::new(RequestDeduplicator::new()),
224 eager_types: std::collections::HashSet::new(),
225 }
226 }
227
228 pub fn add_resolver(&mut self, type_name: String, resolver: Arc<dyn FieldResolver>) {
229 self.resolvers.insert(type_name, resolver);
230 }
231
232 pub fn add_eager_type(&mut self, type_name: impl Into<String>) {
236 self.eager_types.insert(type_name.into());
237 }
238
239 pub async fn execute(
240 &self,
241 document: &Document,
242 context: &ExecutionContext,
243 ) -> Result<ExecutionResult> {
244 let op_name = context.operation_name.clone().unwrap_or_default();
253 let vars_str = format!("{:?}", context.variables);
254 let document_hash = Self::hash_document(document);
255 let deduplication_key = format!("{op_name}:{document_hash}:{vars_str}");
256
257 self.deduplicator
258 .deduplicate(deduplication_key, || async move {
259 self.execute_internal(document, context).await
260 })
261 .await
262 }
263
264 fn hash_document(document: &Document) -> String {
269 use sha2::{Digest, Sha256};
270 let serialized =
271 serde_json::to_vec(document).unwrap_or_else(|_| format!("{document:?}").into_bytes());
272 let mut hasher = Sha256::new();
273 hasher.update(&serialized);
274 hex::encode(hasher.finalize())
275 }
276
277 async fn execute_internal(
278 &self,
279 document: &Document,
280 context: &ExecutionContext,
281 ) -> Result<ExecutionResult> {
282 let schema = self.schema.read().await;
283
284 let mut execution_context = context.clone();
286 self.collect_fragments(document, &mut execution_context)?;
287
288 let operation = self.get_operation(document, &execution_context.operation_name)?;
290
291 match operation.operation_type {
293 OperationType::Query => {
294 let query_type = schema
295 .query_type
296 .as_ref()
297 .ok_or_else(|| anyhow!("Schema does not define a query type"))?;
298
299 self.execute_selection_set(
300 &operation.selection_set,
301 query_type,
302 &execution_context,
303 &schema,
304 Vec::new(),
305 )
306 .await
307 }
308 OperationType::Mutation => {
309 let mutation_type = schema
310 .mutation_type
311 .as_ref()
312 .ok_or_else(|| anyhow!("Schema does not define a mutation type"))?;
313
314 self.execute_selection_set(
315 &operation.selection_set,
316 mutation_type,
317 &execution_context,
318 &schema,
319 Vec::new(),
320 )
321 .await
322 }
323 OperationType::Subscription => {
324 let subscription_type = schema
325 .subscription_type
326 .as_ref()
327 .ok_or_else(|| anyhow!("Schema does not define a subscription type"))?;
328
329 self.execute_selection_set(
330 &operation.selection_set,
331 subscription_type,
332 &execution_context,
333 &schema,
334 Vec::new(),
335 )
336 .await
337 }
338 }
339 }
340
341 fn collect_fragments(&self, document: &Document, context: &mut ExecutionContext) -> Result<()> {
342 for definition in &document.definitions {
343 if let crate::ast::Definition::Fragment(fragment) = definition {
344 context.add_fragment(fragment.name.clone(), fragment.clone());
345 }
346 }
347 Ok(())
348 }
349
350 fn get_operation<'a>(
351 &self,
352 document: &'a Document,
353 operation_name: &Option<String>,
354 ) -> Result<&'a OperationDefinition> {
355 let operations: Vec<_> = document
356 .definitions
357 .iter()
358 .filter_map(|def| match def {
359 crate::ast::Definition::Operation(op) => Some(op),
360 _ => None,
361 })
362 .collect();
363
364 match (operations.len(), operation_name) {
365 (0, _) => Err(anyhow!("No operations found in document")),
366 (1, _) => Ok(operations[0]),
367 (_, Some(name)) => operations
368 .iter()
369 .find(|op| op.name.as_ref() == Some(name))
370 .copied()
371 .ok_or_else(|| anyhow!("Operation '{}' not found", name)),
372 (_, None) => Err(anyhow!(
373 "Multiple operations found but no operation name specified"
374 )),
375 }
376 }
377
378 fn execute_selection_set<'a>(
379 &'a self,
380 selection_set: &'a SelectionSet,
381 type_name: &'a str,
382 context: &'a ExecutionContext,
383 schema: &'a Schema,
384 path: Vec<String>,
385 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ExecutionResult>> + Send + 'a>>
386 {
387 Box::pin(async move {
388 let mut result_data = HashMap::new();
389 let mut errors = Vec::new();
390
391 for selection in &selection_set.selections {
392 match selection {
393 Selection::Field(field) => {
394 let field_path = {
395 let mut p = path.clone();
396 p.push(field.alias.as_ref().unwrap_or(&field.name).clone());
397 p
398 };
399
400 match self
401 .execute_field(field, type_name, context, schema, field_path.clone())
402 .await
403 {
404 Ok(value) => {
405 let field_name = field.alias.as_ref().unwrap_or(&field.name);
406 result_data.insert(field_name.clone(), value);
407 }
408 Err(err) => {
409 errors
410 .push(GraphQLError::new(err.to_string()).with_path(field_path));
411 }
412 }
413 }
414 Selection::InlineFragment(inline_fragment) => {
415 match self
416 .execute_inline_fragment(
417 inline_fragment,
418 type_name,
419 context,
420 schema,
421 path.clone(),
422 )
423 .await
424 {
425 Ok(fragment_data) => {
426 if let Some(fragment_object) = fragment_data.as_object() {
428 for (key, value) in fragment_object {
429 result_data.insert(key.clone(), value.clone());
430 }
431 }
432 }
433 Err(err) => {
434 errors.push(
435 GraphQLError::new(err.to_string()).with_path(path.clone()),
436 );
437 }
438 }
439 }
440 Selection::FragmentSpread(fragment_spread) => {
441 match self
442 .execute_fragment_spread(
443 fragment_spread,
444 type_name,
445 context,
446 schema,
447 path.clone(),
448 )
449 .await
450 {
451 Ok(fragment_data) => {
452 if let Some(fragment_object) = fragment_data.as_object() {
454 for (key, value) in fragment_object {
455 result_data.insert(key.clone(), value.clone());
456 }
457 }
458 }
459 Err(err) => {
460 errors.push(
461 GraphQLError::new(err.to_string()).with_path(path.clone()),
462 );
463 }
464 }
465 }
466 }
467 }
468
469 let data = serde_json::to_value(result_data)
470 .map_err(|e| anyhow!("Failed to serialize result data: {}", e))?;
471
472 Ok(ExecutionResult {
473 data: Some(data),
474 errors,
475 })
476 })
477 }
478
479 async fn execute_field(
480 &self,
481 field: &Field,
482 parent_type: &str,
483 context: &ExecutionContext,
484 schema: &Schema,
485 path: Vec<String>,
486 ) -> Result<JsonValue> {
487 let args = self.collect_field_arguments(&field.arguments, context)?;
489
490 let field_type = self.get_field_type(parent_type, &field.name, schema)?;
492
493 let field_value = if let Some(resolver) = self.resolvers.get(parent_type) {
495 resolver.resolve_field(&field.name, &args, context).await?
496 } else {
497 Value::NullValue
499 };
500
501 self.complete_value(
503 field_type,
504 &field_value,
505 &field.selection_set,
506 context,
507 schema,
508 path,
509 )
510 .await
511 }
512
513 fn collect_field_arguments(
514 &self,
515 arguments: &[crate::ast::Argument],
516 context: &ExecutionContext,
517 ) -> Result<HashMap<String, Value>> {
518 let mut args = HashMap::new();
519
520 for arg in arguments {
521 let value = self.resolve_value(&arg.value, context)?;
522 args.insert(arg.name.clone(), value);
523 }
524
525 Ok(args)
526 }
527
528 fn resolve_value(&self, value: &Value, context: &ExecutionContext) -> Result<Value> {
529 match value {
530 Value::Variable(var) => context
531 .variables
532 .get(&var.name)
533 .cloned()
534 .ok_or_else(|| anyhow!("Variable '{}' not defined", var.name)),
535 _ => Ok(value.clone()),
536 }
537 }
538
539 fn get_field_type<'a>(
540 &self,
541 parent_type: &str,
542 field_name: &str,
543 schema: &'a Schema,
544 ) -> Result<&'a GraphQLType> {
545 let parent_type_def = schema
546 .get_type(parent_type)
547 .ok_or_else(|| anyhow!("Type '{}' not found in schema", parent_type))?;
548
549 match parent_type_def {
550 GraphQLType::Object(obj) => obj
551 .fields
552 .get(field_name)
553 .map(|field| &field.field_type)
554 .ok_or_else(|| {
555 anyhow!("Field '{}' not found on type '{}'", field_name, parent_type)
556 }),
557 GraphQLType::Interface(iface) => iface
558 .fields
559 .get(field_name)
560 .map(|field| &field.field_type)
561 .ok_or_else(|| {
562 anyhow!(
563 "Field '{}' not found on interface '{}'",
564 field_name,
565 parent_type
566 )
567 }),
568 _ => Err(anyhow!(
569 "Cannot select field '{}' on non-composite type '{}'",
570 field_name,
571 parent_type
572 )),
573 }
574 }
575
576 fn complete_value<'a>(
577 &'a self,
578 field_type: &'a GraphQLType,
579 value: &'a Value,
580 selection_set: &'a Option<SelectionSet>,
581 context: &'a ExecutionContext,
582 schema: &'a Schema,
583 path: Vec<String>,
584 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<JsonValue>> + Send + 'a>> {
585 Box::pin(async move {
586 match field_type {
587 GraphQLType::NonNull(inner_type) => {
588 if matches!(value, Value::NullValue) {
589 return Err(anyhow!("Cannot return null for non-null field"));
590 }
591 self.complete_value(inner_type, value, selection_set, context, schema, path)
592 .await
593 }
594 GraphQLType::List(inner_type) => match value {
595 Value::ListValue(list) => {
596 let mut result = Vec::new();
597 for (i, item) in list.iter().enumerate() {
598 let item_path = {
599 let mut p = path.clone();
600 p.push(i.to_string());
601 p
602 };
603 let completed = self
604 .complete_value(
605 inner_type,
606 item,
607 selection_set,
608 context,
609 schema,
610 item_path,
611 )
612 .await?;
613 result.push(completed);
614 }
615 Ok(JsonValue::Array(result))
616 }
617 Value::NullValue => Ok(JsonValue::Null),
618 _ => Err(anyhow!("Expected list value but got {:?}", value)),
619 },
620 GraphQLType::Scalar(_) => {
621 self.serialize_scalar_value(value)
623 }
624 GraphQLType::Object(_) | GraphQLType::Interface(_) => {
625 if let Some(selection_set) = selection_set {
626 if matches!(value, Value::NullValue) {
627 return Ok(JsonValue::Null);
628 }
629
630 let type_name = field_type.name();
631 if type_name.starts_with("__") || self.eager_types.contains(type_name) {
632 return self.project_introspection_value(
649 type_name,
650 value,
651 selection_set,
652 context,
653 schema,
654 );
655 }
656
657 let result = self
659 .execute_selection_set(
660 selection_set,
661 field_type.name(),
662 context,
663 schema,
664 path,
665 )
666 .await?;
667
668 result
669 .data
670 .ok_or_else(|| anyhow!("No data returned from sub-selection"))
671 } else {
672 Err(anyhow!("Selection set required for object/interface type"))
673 }
674 }
675 GraphQLType::Union(union_type) => {
676 self.complete_union_value(
678 union_type,
679 value,
680 selection_set,
681 context,
682 schema,
683 path,
684 )
685 .await
686 }
687 GraphQLType::Enum(enum_type) => {
688 self.complete_enum_value(enum_type, value)
690 }
691 GraphQLType::InputObject(_) => {
692 Err(anyhow!("Input object types cannot be used as output types"))
693 }
694 }
695 })
696 }
697
698 #[allow(clippy::only_used_in_recursion)]
699 fn serialize_scalar_value(&self, value: &Value) -> Result<JsonValue> {
700 match value {
701 Value::NullValue => Ok(JsonValue::Null),
702 Value::IntValue(i) => Ok(JsonValue::Number((*i).into())),
703 Value::FloatValue(f) => serde_json::Number::from_f64(*f)
704 .map(JsonValue::Number)
705 .ok_or_else(|| anyhow!("Invalid float value: {}", f)),
706 Value::StringValue(s) => Ok(JsonValue::String(s.clone())),
707 Value::BooleanValue(b) => Ok(JsonValue::Bool(*b)),
708 Value::EnumValue(s) => Ok(JsonValue::String(s.clone())),
709 Value::ListValue(list) => {
710 let json_list: Result<Vec<JsonValue>> = list
711 .iter()
712 .map(|v| self.serialize_scalar_value(v))
713 .collect();
714 Ok(JsonValue::Array(json_list?))
715 }
716 Value::ObjectValue(obj) => {
717 let json_obj: Result<serde_json::Map<String, JsonValue>> = obj
718 .iter()
719 .map(|(k, v)| {
720 self.serialize_scalar_value(v)
721 .map(|json_v| (k.clone(), json_v))
722 })
723 .collect();
724 Ok(JsonValue::Object(json_obj?))
725 }
726 Value::Variable(_) => Err(anyhow!("Variables should be resolved before serialization")),
727 }
728 }
729
730 async fn execute_inline_fragment(
731 &self,
732 inline_fragment: &crate::ast::InlineFragment,
733 parent_type: &str,
734 context: &ExecutionContext,
735 schema: &Schema,
736 path: Vec<String>,
737 ) -> Result<JsonValue> {
738 if let Some(ref type_condition) = inline_fragment.type_condition {
740 let fragment_context = FragmentContext::new(parent_type.to_string());
741 if !fragment_context.can_apply_fragment(type_condition) {
742 return Ok(JsonValue::Object(serde_json::Map::new()));
744 }
745 }
746
747 let result = self
749 .execute_selection_set(
750 &inline_fragment.selection_set,
751 parent_type,
752 context,
753 schema,
754 path,
755 )
756 .await?;
757
758 result
759 .data
760 .ok_or_else(|| anyhow!("No data returned from inline fragment"))
761 }
762
763 async fn execute_fragment_spread(
764 &self,
765 fragment_spread: &crate::ast::FragmentSpread,
766 parent_type: &str,
767 context: &ExecutionContext,
768 schema: &Schema,
769 path: Vec<String>,
770 ) -> Result<JsonValue> {
771 let fragment_def = context
773 .fragments
774 .get(&fragment_spread.fragment_name)
775 .ok_or_else(|| anyhow!("Fragment '{}' not found", fragment_spread.fragment_name))?;
776
777 let fragment_context = FragmentContext::new(parent_type.to_string());
779 if !fragment_context.can_apply_fragment(&fragment_def.type_condition) {
780 return Ok(JsonValue::Object(serde_json::Map::new()));
782 }
783
784 let result = self
786 .execute_selection_set(
787 &fragment_def.selection_set,
788 parent_type,
789 context,
790 schema,
791 path,
792 )
793 .await?;
794
795 result
796 .data
797 .ok_or_else(|| anyhow!("No data returned from fragment spread"))
798 }
799
800 async fn complete_union_value(
801 &self,
802 union_type: &crate::types::UnionType,
803 value: &Value,
804 selection_set: &Option<SelectionSet>,
805 context: &ExecutionContext,
806 schema: &Schema,
807 path: Vec<String>,
808 ) -> Result<JsonValue> {
809 if matches!(value, Value::NullValue) {
810 return Ok(JsonValue::Null);
811 }
812
813 let concrete_type = self.resolve_union_type(union_type, value, schema)?;
818
819 if let Some(selection_set) = selection_set {
820 let result = self
821 .execute_selection_set(selection_set, &concrete_type, context, schema, path)
822 .await?;
823
824 let mut object_result = result
825 .data
826 .unwrap_or(JsonValue::Object(serde_json::Map::new()));
827
828 if let JsonValue::Object(ref mut obj) = object_result {
830 obj.insert("__typename".to_string(), JsonValue::String(concrete_type));
831 }
832
833 Ok(object_result)
834 } else {
835 Err(anyhow!("Selection set required for union type"))
836 }
837 }
838
839 fn complete_enum_value(
840 &self,
841 enum_type: &crate::types::EnumType,
842 value: &Value,
843 ) -> Result<JsonValue> {
844 match value {
845 Value::NullValue => Ok(JsonValue::Null),
846 Value::EnumValue(enum_val) => {
847 if enum_type.values.contains_key(enum_val) {
849 Ok(JsonValue::String(enum_val.clone()))
850 } else {
851 Err(anyhow!(
852 "Invalid enum value '{}' for enum type '{}'",
853 enum_val,
854 enum_type.name
855 ))
856 }
857 }
858 Value::StringValue(string_val) => {
859 if enum_type.values.contains_key(string_val) {
861 Ok(JsonValue::String(string_val.clone()))
862 } else {
863 Err(anyhow!(
864 "Invalid enum value '{}' for enum type '{}'",
865 string_val,
866 enum_type.name
867 ))
868 }
869 }
870 _ => Err(anyhow!(
871 "Cannot coerce {:?} to enum type '{}'",
872 value,
873 enum_type.name
874 )),
875 }
876 }
877
878 fn resolve_union_type(
879 &self,
880 union_type: &crate::types::UnionType,
881 value: &Value,
882 schema: &Schema,
883 ) -> Result<String> {
884 if union_type.types.is_empty() {
891 return Err(anyhow!(
892 "Union type '{}' has no possible types",
893 union_type.name
894 ));
895 }
896
897 match value {
898 Value::ObjectValue(obj) => {
899 for type_name in &union_type.types {
901 if let Some(GraphQLType::Object(object_type)) = schema.get_type(type_name) {
902 let has_matching_fields =
904 obj.keys().any(|key| object_type.fields.contains_key(key));
905
906 if has_matching_fields {
907 return Ok(type_name.clone());
908 }
909 }
910 }
911
912 Err(anyhow!(
917 "Unable to resolve concrete type for union '{}': no member type ({}) has fields matching the resolved object's keys ({:?})",
918 union_type.name,
919 union_type.types.join(", "),
920 obj.keys().collect::<Vec<_>>()
921 ))
922 }
923 _ => Err(anyhow!(
924 "Unable to resolve concrete type for union '{}': resolved value is not an object ({:?}), so no member type can be determined",
925 union_type.name,
926 value
927 )),
928 }
929 }
930
931 fn project_introspection_value(
946 &self,
947 type_name: &str,
948 value: &Value,
949 selection_set: &SelectionSet,
950 context: &ExecutionContext,
951 schema: &Schema,
952 ) -> Result<JsonValue> {
953 match value {
954 Value::NullValue => Ok(JsonValue::Null),
955 Value::ListValue(items) => {
956 let mut result = Vec::with_capacity(items.len());
957 for item in items {
958 result.push(self.project_introspection_value(
959 type_name,
960 item,
961 selection_set,
962 context,
963 schema,
964 )?);
965 }
966 Ok(JsonValue::Array(result))
967 }
968 Value::ObjectValue(obj) => {
969 let mut result = serde_json::Map::new();
970 self.project_selections_into(
971 type_name,
972 obj,
973 &selection_set.selections,
974 context,
975 schema,
976 &mut result,
977 )?;
978 Ok(JsonValue::Object(result))
979 }
980 other => self.serialize_scalar_value(other),
981 }
982 }
983
984 fn project_selections_into(
991 &self,
992 type_name: &str,
993 obj: &HashMap<String, Value>,
994 selections: &[Selection],
995 context: &ExecutionContext,
996 schema: &Schema,
997 result: &mut serde_json::Map<String, JsonValue>,
998 ) -> Result<()> {
999 for selection in selections {
1000 match selection {
1001 Selection::Field(field) => {
1002 let response_key = field.alias.clone().unwrap_or_else(|| field.name.clone());
1003
1004 if field.name == "__typename" {
1005 result.insert(response_key, JsonValue::String(type_name.to_string()));
1006 continue;
1007 }
1008
1009 let sub_value = obj.get(&field.name).cloned().unwrap_or(Value::NullValue);
1010
1011 let projected = match &field.selection_set {
1012 Some(sub_selection) => {
1013 let sub_type_name = self
1014 .lookup_object_field_type_name(type_name, &field.name, schema)
1015 .unwrap_or_else(|| type_name.to_string());
1016 self.project_introspection_value(
1017 &sub_type_name,
1018 &sub_value,
1019 sub_selection,
1020 context,
1021 schema,
1022 )?
1023 }
1024 None => self.serialize_scalar_value(&sub_value)?,
1025 };
1026
1027 result.insert(response_key, projected);
1028 }
1029 Selection::InlineFragment(inline_fragment) => {
1030 self.project_selections_into(
1031 type_name,
1032 obj,
1033 &inline_fragment.selection_set.selections,
1034 context,
1035 schema,
1036 result,
1037 )?;
1038 }
1039 Selection::FragmentSpread(fragment_spread) => {
1040 let fragment_def = context
1041 .fragments
1042 .get(&fragment_spread.fragment_name)
1043 .ok_or_else(|| {
1044 anyhow!("Fragment '{}' not found", fragment_spread.fragment_name)
1045 })?;
1046 self.project_selections_into(
1047 type_name,
1048 obj,
1049 &fragment_def.selection_set.selections,
1050 context,
1051 schema,
1052 result,
1053 )?;
1054 }
1055 }
1056 }
1057 Ok(())
1058 }
1059
1060 fn lookup_object_field_type_name(
1064 &self,
1065 type_name: &str,
1066 field_name: &str,
1067 schema: &Schema,
1068 ) -> Option<String> {
1069 let GraphQLType::Object(object_type) = schema.get_type(type_name)? else {
1070 return None;
1071 };
1072 let field = object_type.fields.get(field_name)?;
1073 Some(field.field_type.name().to_string())
1074 }
1075}
1076
1077pub struct DefaultResolver;
1079
1080#[async_trait]
1081impl FieldResolver for DefaultResolver {
1082 async fn resolve_field(
1083 &self,
1084 field_name: &str,
1085 _args: &HashMap<String, Value>,
1086 _context: &ExecutionContext,
1087 ) -> Result<Value> {
1088 match field_name {
1091 "hello" => Ok(Value::StringValue("Hello, World!".to_string())),
1092 "id" => Ok(Value::StringValue(uuid::Uuid::new_v4().to_string())),
1093 "__typename" => Ok(Value::StringValue("Query".to_string())), _ => Ok(Value::NullValue),
1095 }
1096 }
1097}
1098
1099pub struct TypeInfoResolver {
1101 type_name: String,
1102}
1103
1104impl TypeInfoResolver {
1105 pub fn new(type_name: String) -> Self {
1106 Self { type_name }
1107 }
1108}
1109
1110#[async_trait]
1111impl FieldResolver for TypeInfoResolver {
1112 async fn resolve_field(
1113 &self,
1114 field_name: &str,
1115 _args: &HashMap<String, Value>,
1116 _context: &ExecutionContext,
1117 ) -> Result<Value> {
1118 match field_name {
1119 "__typename" => Ok(Value::StringValue(self.type_name.clone())),
1120 _ => Ok(Value::NullValue),
1121 }
1122 }
1123}
1124
1125#[cfg(test)]
1126mod union_type_resolution_tests {
1127 use super::*;
1128 use crate::types::{BuiltinScalars, FieldType, ObjectType, UnionType};
1129
1130 fn build_schema_with_union() -> (Schema, UnionType) {
1131 let mut schema = Schema::new();
1132
1133 let cat_type = ObjectType::new("Cat".to_string()).with_field(
1134 "meow".to_string(),
1135 FieldType::new(
1136 "meow".to_string(),
1137 GraphQLType::Scalar(BuiltinScalars::boolean()),
1138 ),
1139 );
1140 let dog_type = ObjectType::new("Dog".to_string()).with_field(
1141 "bark".to_string(),
1142 FieldType::new(
1143 "bark".to_string(),
1144 GraphQLType::Scalar(BuiltinScalars::boolean()),
1145 ),
1146 );
1147
1148 schema.add_type(GraphQLType::Object(cat_type));
1149 schema.add_type(GraphQLType::Object(dog_type));
1150
1151 let union_type = UnionType::new("Pet".to_string())
1152 .with_type("Cat".to_string())
1153 .with_type("Dog".to_string());
1154
1155 (schema, union_type)
1156 }
1157
1158 #[test]
1162 fn test_resolve_union_type_matches_correct_member() {
1163 let (schema, union_type) = build_schema_with_union();
1164 let executor = QueryExecutor::new(schema.clone());
1165
1166 let mut obj = HashMap::new();
1167 obj.insert("bark".to_string(), Value::BooleanValue(true));
1168 let value = Value::ObjectValue(obj);
1169
1170 let resolved = executor
1171 .resolve_union_type(&union_type, &value, &schema)
1172 .expect("should resolve to Dog");
1173 assert_eq!(resolved, "Dog");
1174 }
1175
1176 #[test]
1182 fn test_resolve_union_type_errors_instead_of_guessing_first_member() {
1183 let (schema, union_type) = build_schema_with_union();
1184 let executor = QueryExecutor::new(schema.clone());
1185
1186 let mut obj = HashMap::new();
1187 obj.insert(
1188 "unrelated_field".to_string(),
1189 Value::StringValue("x".to_string()),
1190 );
1191 let value = Value::ObjectValue(obj);
1192
1193 let result = executor.resolve_union_type(&union_type, &value, &schema);
1194 assert!(
1195 result.is_err(),
1196 "expected an error instead of a guessed first-member fallback, got {result:?}"
1197 );
1198 }
1199
1200 #[test]
1203 fn test_resolve_union_type_errors_on_non_object_value() {
1204 let (schema, union_type) = build_schema_with_union();
1205 let executor = QueryExecutor::new(schema.clone());
1206
1207 let result = executor.resolve_union_type(&union_type, &Value::IntValue(1), &schema);
1208 assert!(result.is_err());
1209 }
1210
1211 #[test]
1213 fn test_resolve_union_type_errors_on_empty_union() {
1214 let schema = Schema::new();
1215 let executor = QueryExecutor::new(schema.clone());
1216 let empty_union = UnionType::new("Empty".to_string());
1217
1218 let result =
1219 executor.resolve_union_type(&empty_union, &Value::ObjectValue(HashMap::new()), &schema);
1220 assert!(result.is_err());
1221 }
1222}
1223
1224#[cfg(test)]
1225mod dedup_key_regression_tests {
1226 use super::*;
1227
1228 #[test]
1229 fn regression_hash_document_distinguishes_different_queries() {
1230 let a = crate::parser::parse_document("{ subjects }").expect("parse a");
1231 let b = crate::parser::parse_document("{ predicates }").expect("parse b");
1232 let ha = QueryExecutor::hash_document(&a);
1233 let hb = QueryExecutor::hash_document(&b);
1234 assert_ne!(ha, hb, "distinct documents must hash differently");
1236 }
1237
1238 #[test]
1239 fn regression_hash_document_stable_for_identical_queries() {
1240 let a = crate::parser::parse_document("{ subjects objects }").expect("parse a");
1241 let b = crate::parser::parse_document("{ subjects objects }").expect("parse b");
1242 assert_eq!(
1243 QueryExecutor::hash_document(&a),
1244 QueryExecutor::hash_document(&b),
1245 "identical documents must hash identically"
1246 );
1247 }
1248}
1249
1250#[cfg(test)]
1251mod introspection_projection_tests {
1252 use super::*;
1253 use crate::introspection::introspection_meta_types;
1254 use crate::types::{FieldType, ObjectType};
1255
1256 fn selection_set_for_field(query: &str, field_name: &str) -> SelectionSet {
1257 let document = crate::parser::parse_document(query).expect("query should parse");
1258 for definition in document.definitions {
1259 if let crate::ast::Definition::Operation(op) = definition {
1260 for selection in op.selection_set.selections {
1261 if let Selection::Field(field) = selection {
1262 if field.name == field_name {
1263 return field
1264 .selection_set
1265 .expect("field should have a selection set");
1266 }
1267 }
1268 }
1269 }
1270 }
1271 panic!("field '{field_name}' not found in query");
1272 }
1273
1274 fn build_schema() -> Schema {
1275 let mut schema = Schema::new();
1276 for meta_type in introspection_meta_types() {
1277 schema.add_type(meta_type);
1278 }
1279
1280 let query_type = ObjectType::new("Query".to_string()).with_field(
1281 "__schema".to_string(),
1282 FieldType::new(
1283 "__schema".to_string(),
1284 GraphQLType::Object(ObjectType::new("__Schema".to_string())),
1285 ),
1286 );
1287 schema.add_type(GraphQLType::Object(query_type));
1288 schema.set_query_type("Query".to_string());
1289 schema
1290 }
1291
1292 #[test]
1298 fn test_project_introspection_value_honors_nested_selection() {
1299 let schema = build_schema();
1300 let executor = QueryExecutor::new(schema.clone());
1301 let context = ExecutionContext::new();
1302
1303 let selection_set = selection_set_for_field(
1304 "query { __schema { queryType { name kind } directives { name } } }",
1305 "__schema",
1306 );
1307
1308 let mut query_type_obj = HashMap::new();
1311 query_type_obj.insert("name".to_string(), Value::StringValue("Query".to_string()));
1312 query_type_obj.insert("kind".to_string(), Value::EnumValue("OBJECT".to_string()));
1313 query_type_obj.insert("description".to_string(), Value::NullValue);
1314
1315 let mut directive_obj = HashMap::new();
1316 directive_obj.insert("name".to_string(), Value::StringValue("skip".to_string()));
1317
1318 let mut schema_obj = HashMap::new();
1319 schema_obj.insert("queryType".to_string(), Value::ObjectValue(query_type_obj));
1320 schema_obj.insert(
1321 "directives".to_string(),
1322 Value::ListValue(vec![Value::ObjectValue(directive_obj)]),
1323 );
1324 schema_obj.insert("mutationType".to_string(), Value::NullValue);
1325
1326 let value = Value::ObjectValue(schema_obj);
1327
1328 let json = executor
1329 .project_introspection_value("__Schema", &value, &selection_set, &context, &schema)
1330 .expect("projection should succeed");
1331
1332 assert_eq!(json["queryType"]["name"], serde_json::json!("Query"));
1333 assert_eq!(json["queryType"]["kind"], serde_json::json!("OBJECT"));
1334 assert_eq!(json["directives"][0]["name"], serde_json::json!("skip"));
1335 }
1336
1337 #[test]
1341 fn test_project_introspection_value_supports_typename() {
1342 let schema = build_schema();
1343 let executor = QueryExecutor::new(schema.clone());
1344 let context = ExecutionContext::new();
1345
1346 let selection_set = selection_set_for_field(
1347 "query { __schema { __typename queryType { __typename name } } }",
1348 "__schema",
1349 );
1350
1351 let mut query_type_obj = HashMap::new();
1352 query_type_obj.insert("name".to_string(), Value::StringValue("Query".to_string()));
1353
1354 let mut schema_obj = HashMap::new();
1355 schema_obj.insert("queryType".to_string(), Value::ObjectValue(query_type_obj));
1356
1357 let value = Value::ObjectValue(schema_obj);
1358
1359 let json = executor
1360 .project_introspection_value("__Schema", &value, &selection_set, &context, &schema)
1361 .expect("projection should succeed");
1362
1363 assert_eq!(json["__typename"], serde_json::json!("__Schema"));
1364 assert_eq!(json["queryType"]["__typename"], serde_json::json!("__Type"));
1365 }
1366
1367 #[test]
1370 fn test_project_introspection_value_null_short_circuits() {
1371 let schema = build_schema();
1372 let executor = QueryExecutor::new(schema.clone());
1373 let context = ExecutionContext::new();
1374
1375 let selection_set =
1376 selection_set_for_field("query { __schema { queryType { name } } }", "__schema");
1377
1378 let json = executor
1379 .project_introspection_value(
1380 "__Schema",
1381 &Value::NullValue,
1382 &selection_set,
1383 &context,
1384 &schema,
1385 )
1386 .expect("projection of null should succeed");
1387
1388 assert_eq!(json, serde_json::Value::Null);
1389 }
1390}