use crate::context::Data; use crate::error::ParseRequestError; use crate::mutation_resolver::do_mutation_resolve; use crate::registry::CacheControl; use crate::validation::{check_rules, CheckResult}; use crate::{do_resolve, ContextBase, Error, Result, Schema}; use crate::{ObjectType, QueryError, Variables}; use graphql_parser::query::{ Definition, Document, OperationDefinition, SelectionSet, VariableDefinition, }; use graphql_parser::{parse_query, Pos}; use itertools::Itertools; use std::any::Any; use std::collections::HashMap; use std::path::Path; use std::sync::atomic::AtomicUsize; use tempdir::TempDir; #[allow(missing_docs)] #[async_trait::async_trait] pub trait IntoQueryBuilder { async fn into_query_builder(self) -> std::result::Result; } /// Query response pub struct QueryResponse { /// Data of query result pub data: serde_json::Value, /// Extensions result pub extensions: Option>, /// Cache control value pub cache_control: CacheControl, } /// Query builder pub struct QueryBuilder { pub(crate) query_source: String, pub(crate) operation_name: Option, pub(crate) variables: Variables, pub(crate) ctx_data: Option, pub(crate) files_holder: Option, } impl QueryBuilder { /// Create query builder with query source. pub fn new>(query_source: T) -> QueryBuilder { QueryBuilder { query_source: query_source.into(), operation_name: None, variables: Default::default(), ctx_data: None, files_holder: 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 } /// Set file holder pub fn set_files_holder(&mut self, files_holder: TempDir) { self.files_holder = Some(files_holder); } /// Set uploaded file path pub fn set_upload( &mut self, var_path: &str, filename: &str, content_type: Option<&str>, path: &Path, ) { self.variables .set_upload(var_path, filename, content_type, path); } /// Execute the query. pub async fn execute( self, schema: &Schema, ) -> Result where Query: ObjectType + Send + Sync, Mutation: ObjectType + Send + Sync, { // create extension instances let extensions = schema .0 .extensions .iter() .map(|factory| factory()) .collect_vec(); // parse query source extensions .iter() .for_each(|e| e.parse_start(&self.query_source)); let document = parse_query(&self.query_source).map_err(Into::::into)?; extensions.iter().for_each(|e| e.parse_end()); // check rules extensions.iter().for_each(|e| e.validation_start()); let CheckResult { cache_control, complexity, depth, } = check_rules(&schema.0.registry, &document, schema.0.validation_mode)?; extensions.iter().for_each(|e| e.validation_end()); // check limit if let Some(limit_complexity) = schema.0.complexity { if complexity > limit_complexity { return Err(QueryError::TooComplex.into_error(Pos::default())); } } if let Some(limit_depth) = schema.0.depth { if depth > limit_depth { return Err(QueryError::TooDeep.into_error(Pos::default())); } } // execute let resolve_id = AtomicUsize::default(); let mut fragments = HashMap::new(); let (selection_set, variable_definitions, is_query) = current_operation(&document, self.operation_name.as_deref()).ok_or_else(|| { Error::Query { pos: Pos::default(), path: None, err: QueryError::MissingOperation, } })?; for definition in &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: &extensions, item: selection_set, variables: &self.variables, variable_definitions, registry: &schema.0.registry, data: &schema.0.data, ctx_data: self.ctx_data.as_ref(), fragments: &fragments, }; extensions.iter().for_each(|e| e.execution_start()); let data = if is_query { do_resolve(&ctx, &schema.0.query).await? } else { do_mutation_resolve(&ctx, &schema.0.mutation).await? }; extensions.iter().for_each(|e| e.execution_end()); let res = QueryResponse { data, extensions: if !extensions.is_empty() { Some( extensions .iter() .map(|e| (e.name().to_string(), e.result())) .collect::>(), ) } else { None }, cache_control, }; Ok(res) } } fn current_operation<'a>( document: &'a Document, operation_name: Option<&str>, ) -> Option<(&'a SelectionSet, &'a [VariableDefinition], bool)> { for definition in &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() || operation_name.is_none() || query.name.as_deref() == operation_name.as_deref() => { return Some((&query.selection_set, &query.variable_definitions, true)); } OperationDefinition::Mutation(mutation) if mutation.name.is_none() || operation_name.is_none() || mutation.name.as_deref() == operation_name.as_deref() => { return Some(( &mutation.selection_set, &mutation.variable_definitions, false, )); } OperationDefinition::Subscription(subscription) if subscription.name.is_none() || operation_name.is_none() || subscription.name.as_deref() == operation_name.as_deref() => { return None; } _ => {} }, Definition::Fragment(_) => {} } } None }