Skip to main content

oxirs_gql/
execution.rs

1//! GraphQL query execution engine
2//!
3//! This module provides the core execution engine for GraphQL queries,
4//! including field resolution, error handling, and async execution.
5
6use 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/// Execution context containing request-specific data
19#[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/// Fragment execution context for type condition checking
67#[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        // Check if the fragment can be applied to the current type
88        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/// Field resolver trait for resolving GraphQL fields
97#[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/// Execution result containing data and errors
108#[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/// GraphQL execution error
144#[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/// Source location for error reporting
179#[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
191/// GraphQL query executor
192pub struct QueryExecutor {
193    schema: Arc<RwLock<Schema>>,
194    resolvers: HashMap<String, Arc<dyn FieldResolver>>,
195    deduplicator: Arc<RequestDeduplicator>,
196    /// Object type names that must be resolved "eagerly": the resolver
197    /// registered for the *parent* field returns one fully materialized
198    /// [`Value`] tree (covering the requested type and everything nested
199    /// under it), and [`Self::complete_value`] projects the client's
200    /// selection set directly onto that tree via
201    /// [`Self::project_introspection_value`] instead of re-dispatching to
202    /// a resolver keyed by type name.
203    ///
204    /// This matters because the normal Object/Interface dispatch path
205    /// (`execute_selection_set` keyed purely by type name) has no way to
206    /// tell *which* instance of a type it is completing -- it ignores the
207    /// already-resolved `value` entirely and asks the type's resolver to
208    /// resolve each subfield from scratch with no parent context. That is
209    /// correct for singleton root-ish resolvers, but wrong for anything
210    /// resolved as part of a list (every item would render identically).
211    /// Eager types sidestep the issue entirely by never re-dispatching.
212    /// Used by GraphQL introspection (`__Schema`/`__Type`/...) and by
213    /// schemas generated by [`crate::schema_generator::SchemaGenerator`]
214    /// from a store's RDF vocabulary (see `AutoSchemaResolver`).
215    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    /// Mark `type_name` as eagerly resolved. See the `eager_types`
233    /// field documentation for why this is necessary for any Object type
234    /// that can appear as an item inside a `List`.
235    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        // Generate a unique key for deduplication.
245        //
246        // The key MUST incorporate the full query document, not just the
247        // operation name: anonymous queries share an empty operation name, so
248        // keying on name+variables alone would let two unrelated concurrent
249        // anonymous queries with identical variables (e.g. both `{}`) collide
250        // and receive each other's in-flight result. We hash the serialized
251        // document AST so only textually-identical queries deduplicate.
252        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    /// Produce a stable, collision-resistant hex digest of a query document so
265    /// it can safely disambiguate the request-deduplication key. Falls back to
266    /// the Debug representation if serialization ever fails, so distinct
267    /// documents still produce distinct keys.
268    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        // Collect fragments from the document
285        let mut execution_context = context.clone();
286        self.collect_fragments(document, &mut execution_context)?;
287
288        // Find the operation to execute
289        let operation = self.get_operation(document, &execution_context.operation_name)?;
290
291        // Execute based on operation type
292        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                                // Merge fragment data into result
427                                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                                // Merge fragment data into result
453                                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        // Get field arguments
488        let args = self.collect_field_arguments(&field.arguments, context)?;
489
490        // Get the field type from schema
491        let field_type = self.get_field_type(parent_type, &field.name, schema)?;
492
493        // Resolve the field value
494        let field_value = if let Some(resolver) = self.resolvers.get(parent_type) {
495            resolver.resolve_field(&field.name, &args, context).await?
496        } else {
497            // Default resolver - return null for missing resolvers
498            Value::NullValue
499        };
500
501        // Convert to JSON value and handle nested selections
502        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                    // For scalars, convert the value directly
622                    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                            // GraphQL introspection meta-types (__Schema,
633                            // __Type, __Field, ...), and any other type
634                            // explicitly marked via `add_eager_type` (e.g.
635                            // schemas generated from a store's RDF
636                            // vocabulary), are resolved eagerly into one
637                            // fully materialized value tree rather than one
638                            // resolver call per nested field (there is no
639                            // per-type resolver registered for e.g.
640                            // "__Field", and for list items there is no way
641                            // to identify *which* instance a per-type
642                            // resolver should resolve). So instead of
643                            // re-dispatching through execute_selection_set
644                            // (which would look up a resolver by type name
645                            // and ignore `value` entirely), project the
646                            // client's selection set directly onto that
647                            // value. See `Self::eager_types` for details.
648                            return self.project_introspection_value(
649                                type_name,
650                                value,
651                                selection_set,
652                                context,
653                                schema,
654                            );
655                        }
656
657                        // Execute sub-selection
658                        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                    // Union type completion
677                    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                    // Enum type completion
689                    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        // Check type condition if present
739        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                // Fragment doesn't apply to this type, return empty object
743                return Ok(JsonValue::Object(serde_json::Map::new()));
744            }
745        }
746
747        // Execute the fragment's selection set
748        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        // Look up the fragment definition
772        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        // Check type condition
778        let fragment_context = FragmentContext::new(parent_type.to_string());
779        if !fragment_context.can_apply_fragment(&fragment_def.type_condition) {
780            // Fragment doesn't apply to this type, return empty object
781            return Ok(JsonValue::Object(serde_json::Map::new()));
782        }
783
784        // Execute the fragment's selection set
785        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        // For union types, we need to determine the concrete type
814        // This is a simplified implementation - in practice, you'd need
815        // type resolution logic based on the actual data
816
817        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            // Add __typename field for union types
829            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                // Validate that the enum value is valid for this enum type
848                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                // Allow string values to be coerced to enum values
860                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        // This is a simplified type resolution - in practice, you'd need
885        // more sophisticated logic to determine the concrete type
886
887        // For now, we'll use the first available type in the union
888        // or try to infer from the value structure
889
890        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                // Try to determine type based on available fields
900                for type_name in &union_type.types {
901                    if let Some(GraphQLType::Object(object_type)) = schema.get_type(type_name) {
902                        // Check if the object has fields that match this type
903                        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                // No member type's fields matched: guessing the first
913                // declared member would silently mislabel the result (wrong
914                // __typename, wrong selection-set fields applied). Fail
915                // loudly instead so the caller surfaces a GraphQL error.
916                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    /// Project a client selection set directly onto an already-materialized
932    /// introspection value tree (as produced by
933    /// [`crate::introspection::IntrospectionResolver`] for `__schema`/
934    /// `__type` queries), instead of the normal `execute_selection_set`
935    /// path which looks up a resolver keyed by type name and would ignore
936    /// `value` entirely.
937    ///
938    /// `type_name` is the concrete meta-type currently being projected
939    /// (`"__Schema"`, `"__Type"`, `"__Field"`, ...); it is used purely to
940    /// look up each selected field's declared sub-type in `schema` so we
941    /// know which concrete meta-type to recurse into next (and to answer
942    /// `__typename`). It is *not* used to dispatch to a registered
943    /// resolver -- the field's actual data always comes directly from
944    /// `value`.
945    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    /// Apply a flat list of selections (fields, inline fragments, fragment
985    /// spreads) against `obj`, writing the projected key/value pairs into
986    /// `result`. None of these meta-types are polymorphic (no unions or
987    /// multiple concrete implementers), so inline fragments and fragment
988    /// spreads are applied unconditionally rather than checked against a
989    /// type condition.
990    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    /// Look up the declared type name of `field_name` on the object type
1061    /// `type_name` within `schema` (following through `List`/`NonNull`
1062    /// wrappers to the innermost named type).
1063    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
1077/// Default resolver for simple field access
1078pub 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        // Default implementation returns null
1089        // In a real implementation, this would resolve from a data source
1090        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())), // Default typename
1094            _ => Ok(Value::NullValue),
1095        }
1096    }
1097}
1098
1099/// Type information resolver for GraphQL type system
1100pub 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    /// A value whose fields genuinely match one member of the union must
1159    /// still resolve to that member (no regression from the error-instead
1160    /// -of-guessing fix).
1161    #[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    /// Regression test: previously, when no member type's fields matched,
1177    /// `resolve_union_type` silently fell back to `union_type.types[0]`
1178    /// (guessing "Cat" here) instead of surfacing an error. It must now
1179    /// return an `Err` so the caller can turn this into a GraphQL error
1180    /// rather than mislabeling the result with the wrong `__typename`.
1181    #[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    /// A non-object value can never be matched against any member type's
1201    /// field set, so this must also error rather than guess.
1202    #[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    /// A union with no declared member types must error immediately.
1212    #[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        // Two unrelated anonymous queries must NOT share a dedup hash.
1235        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    /// Regression test: `__schema { queryType { name kind } directives { name } }`
1293    /// used to be impossible to honor at all (the field was typed as a bare
1294    /// `Scalar`, so the executor discarded the sub-selection entirely).
1295    /// Nested fields several levels deep must now resolve correctly by
1296    /// projecting the pre-materialized introspection value tree.
1297    #[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        // Build the eagerly-materialized value tree exactly as
1309        // IntrospectionResolver::resolve_schema would.
1310        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    /// `__typename` inside an introspection selection must resolve to the
1338    /// concrete meta-type at whichever depth it's requested, using the
1339    /// schema-declared field type to know what the nested type is.
1340    #[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    /// A `null` introspection value (e.g. no mutation type in the schema)
1368    /// must project to JSON `null` rather than erroring.
1369    #[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}