use crate::context::Data; use crate::extensions::BoxExtension; use crate::mutation_resolver::do_mutation_resolve; use crate::registry::CacheControl; use crate::{do_resolve, ContextBase, Error, Result, Schema}; use crate::{ObjectType, QueryError, Variables}; use bytes::Bytes; use graphql_parser::query::{ Definition, Document, OperationDefinition, SelectionSet, VariableDefinition, }; use graphql_parser::Pos; use std::any::Any; use std::collections::HashMap; use std::sync::atomic::AtomicUsize; /// Query response pub struct QueryResponse { /// Data of query result pub data: serde_json::Value, /// Extensions result pub extensions: Option>, } /// Query builder pub struct QueryBuilder { pub(crate) schema: Schema, pub(crate) extensions: Vec, pub(crate) document: Document, pub(crate) operation_name: Option, pub(crate) variables: Variables, pub(crate) ctx_data: Option, pub(crate) cache_control: CacheControl, } impl QueryBuilder { fn current_operation(&self) -> Option<(&SelectionSet, &[VariableDefinition], bool)> { for definition in &self.document.definitions { match definition { Definition::Operation(operation_definition) => match operation_definition { OperationDefinition::SelectionSet(s) => { return Some((s, &[], true)); } OperationDefinition::Query(query) if query.name.is_none() || query.name.as_deref() == self.operation_name.as_deref() => { return Some((&query.selection_set, &query.variable_definitions, true)); } OperationDefinition::Mutation(mutation) if mutation.name.is_none() || mutation.name.as_deref() == self.operation_name.as_deref() => { return Some(( &mutation.selection_set, &mutation.variable_definitions, false, )); } OperationDefinition::Subscription(subscription) if subscription.name.is_none() || subscription.name.as_deref() == self.operation_name.as_deref() => { return None; } _ => {} }, Definition::Fragment(_) => {} } } None } /// Specify the operation name. pub fn operator_name>(self, name: T) -> Self { QueryBuilder { operation_name: Some(name.into()), ..self } } /// Specify the variables. pub fn variables(self, variables: Variables) -> Self { QueryBuilder { variables, ..self } } /// Add a context data that can be accessed in the `Context`, you access it with `Context::data`. pub fn data(mut self, data: D) -> Self { if let Some(ctx_data) = &mut self.ctx_data { ctx_data.insert(data); } else { let mut ctx_data = Data::default(); ctx_data.insert(data); self.ctx_data = Some(ctx_data); } self } /// Detects whether any parameter contains the Upload type pub fn is_upload(&self) -> bool { if let Some((_, variable_definitions, _)) = self.current_operation() { for d in variable_definitions { if let Some(ty) = self .schema .0 .registry .basic_type_by_parsed_type(&d.var_type) { if ty.name() == "Upload" { return true; } } } } false } /// Set upload files pub fn set_upload( &mut self, var_path: &str, filename: &str, content_type: Option<&str>, content: Bytes, ) { self.variables .set_upload(var_path, filename, content_type, content); } /// Execute the query. pub async fn execute(self) -> Result where Query: ObjectType + Send + Sync, Mutation: ObjectType + Send + Sync, { let resolve_id = AtomicUsize::default(); let mut fragments = HashMap::new(); let (selection_set, variable_definitions, is_query) = self.current_operation().ok_or_else(|| Error::Query { pos: Pos::default(), path: None, err: QueryError::MissingOperation, })?; for definition in &self.document.definitions { if let Definition::Fragment(fragment) = &definition { fragments.insert(fragment.name.clone(), fragment.clone()); } } let ctx = ContextBase { path_node: None, resolve_id: &resolve_id, extensions: &self.extensions, item: selection_set, variables: &self.variables, variable_definitions, registry: &self.schema.0.registry, data: &self.schema.0.data, ctx_data: self.ctx_data.as_ref(), fragments: &fragments, }; self.extensions.iter().for_each(|e| e.execution_start()); let data = if is_query { do_resolve(&ctx, &self.schema.0.query).await? } else { do_mutation_resolve(&ctx, &self.schema.0.mutation).await? }; self.extensions.iter().for_each(|e| e.execution_end()); let res = QueryResponse { data, extensions: if !self.extensions.is_empty() { Some( self.extensions .iter() .map(|e| (e.name().to_string(), e.result())) .collect::>(), ) } else { None }, }; Ok(res) } /// Get cache control value pub fn cache_control(&self) -> CacheControl { self.cache_control } }