use std::{future::Future, pin::Pin, sync::Arc}; use futures_util::FutureExt; use indexmap::IndexMap; use crate::{ extensions::ResolveInfo, parser::types::Selection, Context, ContextBase, ContextSelectionSet, Error, Name, OutputType, ServerError, ServerResult, Value, }; /// Represents a GraphQL container object. /// /// This helper trait allows the type to call `resolve_container` on itself in /// its `OutputType::resolve` implementation. #[async_trait::async_trait] pub trait ContainerType: OutputType { /// This function returns true of type `EmptyMutation` only. #[doc(hidden)] fn is_empty() -> bool { false } /// Resolves a field value and outputs it as a json value /// `async_graphql::Value`. /// /// If the field was not found returns None. async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult>; /// Collect all the fields of the container that are queried in the /// selection set. /// /// Objects do not have to override this, but interfaces and unions must /// call it on their internal type. fn collect_all_fields<'a>( &'a self, ctx: &ContextSelectionSet<'a>, fields: &mut Fields<'a>, ) -> ServerResult<()> where Self: Send + Sync, { fields.add_set(ctx, self) } /// Find the GraphQL entity with the given name from the parameter. /// /// Objects should override this in case they are the query root. async fn find_entity(&self, _: &Context<'_>, _params: &Value) -> ServerResult> { Ok(None) } } #[async_trait::async_trait] impl ContainerType for &T { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { T::resolve_field(*self, ctx).await } async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult> { T::find_entity(*self, ctx, params).await } } #[async_trait::async_trait] impl ContainerType for Arc { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { T::resolve_field(self, ctx).await } async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult> { T::find_entity(self, ctx, params).await } } #[async_trait::async_trait] impl ContainerType for Box { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { T::resolve_field(self, ctx).await } async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult> { T::find_entity(self, ctx, params).await } } #[async_trait::async_trait] impl + Send + Sync + Clone> ContainerType for Result { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { match self { Ok(value) => T::resolve_field(value, ctx).await, Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(ctx.item.pos))), } } async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult> { match self { Ok(value) => T::find_entity(value, ctx, params).await, Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(ctx.item.pos))), } } } /// Resolve an container by executing each of the fields concurrently. pub async fn resolve_container<'a, T: ContainerType + ?Sized>( ctx: &ContextSelectionSet<'a>, root: &'a T, ) -> ServerResult { resolve_container_inner(ctx, root, true).await } /// Resolve an container by executing each of the fields serially. pub async fn resolve_container_serial<'a, T: ContainerType + ?Sized>( ctx: &ContextSelectionSet<'a>, root: &'a T, ) -> ServerResult { resolve_container_inner(ctx, root, false).await } fn insert_value(target: &mut IndexMap, name: Name, value: Value) { if let Some(prev_value) = target.get_mut(&name) { if let Value::Object(target_map) = prev_value { if let Value::Object(obj) = value { for (key, value) in obj.into_iter() { insert_value(target_map, key, value); } } } else if let Value::List(target_list) = prev_value { if let Value::List(list) = value { for (idx, value) in list.into_iter().enumerate() { if let Some(Value::Object(target_map)) = target_list.get_mut(idx) { if let Value::Object(obj) = value { for (key, value) in obj.into_iter() { insert_value(target_map, key, value); } } } } } } } else { target.insert(name, value); } } async fn resolve_container_inner<'a, T: ContainerType + ?Sized>( ctx: &ContextSelectionSet<'a>, root: &'a T, parallel: bool, ) -> ServerResult { let mut fields = Fields(Vec::new()); fields.add_set(ctx, root)?; let res = if parallel { futures_util::future::try_join_all(fields.0).await? } else { let mut results = Vec::with_capacity(fields.0.len()); for field in fields.0 { results.push(field.await?); } results }; let mut map = IndexMap::new(); for (name, value) in res { insert_value(&mut map, name, value); } Ok(Value::Object(map)) } type BoxFieldFuture<'a> = Pin> + 'a + Send>>; /// A set of fields on an container that are being selected. pub struct Fields<'a>(Vec>); impl<'a> Fields<'a> { /// Add another set of fields to this set of fields using the given /// container. pub fn add_set( &mut self, ctx: &ContextSelectionSet<'a>, root: &'a T, ) -> ServerResult<()> { for selection in &ctx.item.node.items { match &selection.node { Selection::Field(field) => { if field.node.name.node == "__typename" { // Get the typename let ctx_field = ctx.with_field(field); let field_name = ctx_field.item.node.response_key().node.clone(); let typename = root.introspection_type_name().into_owned(); self.0.push(Box::pin(async move { Ok((field_name, Value::String(typename))) })); continue; } let resolve_fut = Box::pin({ let ctx = ctx.clone(); async move { let ctx_field = ctx.with_field(field); let field_name = ctx_field.item.node.response_key().node.clone(); let extensions = &ctx.query_env.extensions; if extensions.is_empty() && field.node.directives.is_empty() { Ok(( field_name, root.resolve_field(&ctx_field).await?.unwrap_or_default(), )) } else { let type_name = T::type_name(); let resolve_info = ResolveInfo { path_node: ctx_field.path_node.as_ref().unwrap(), parent_type: &type_name, return_type: match ctx_field .schema_env .registry .types .get(type_name.as_ref()) .and_then(|ty| { ty.field_by_name(field.node.name.node.as_str()) }) .map(|field| &field.ty) { Some(ty) => &ty, None => { return Err(ServerError::new( format!( r#"Cannot query field "{}" on type "{}"."#, field_name, type_name ), Some(ctx_field.item.pos), )); } }, name: field.node.name.node.as_str(), alias: field .node .alias .as_ref() .map(|alias| alias.node.as_str()), is_for_introspection: ctx_field.is_for_introspection, }; let resolve_fut = root.resolve_field(&ctx_field); if field.node.directives.is_empty() { futures_util::pin_mut!(resolve_fut); Ok(( field_name, extensions .resolve(resolve_info, &mut resolve_fut) .await? .unwrap_or_default(), )) } else { let mut resolve_fut = resolve_fut.boxed(); for directive in &field.node.directives { if let Some(directive_factory) = ctx .schema_env .custom_directives .get(directive.node.name.node.as_str()) { let ctx_directive = ContextBase { path_node: ctx_field.path_node, is_for_introspection: false, item: directive, schema_env: ctx_field.schema_env, query_env: ctx_field.query_env, }; let directive_instance = directive_factory .create(&ctx_directive, &directive.node)?; resolve_fut = Box::pin({ let ctx_field = ctx_field.clone(); async move { directive_instance .resolve_field(&ctx_field, &mut resolve_fut) .await } }); } } Ok(( field_name, extensions .resolve(resolve_info, &mut resolve_fut) .await? .unwrap_or_default(), )) } } } }); self.0.push(resolve_fut); } selection => { let (type_condition, selection_set) = match selection { Selection::Field(_) => unreachable!(), Selection::FragmentSpread(spread) => { let fragment = ctx.query_env.fragments.get(&spread.node.fragment_name.node); let fragment = match fragment { Some(fragment) => fragment, None => { return Err(ServerError::new( format!( r#"Unknown fragment "{}"."#, spread.node.fragment_name.node ), Some(spread.pos), )); } }; ( Some(&fragment.node.type_condition), &fragment.node.selection_set, ) } Selection::InlineFragment(fragment) => ( fragment.node.type_condition.as_ref(), &fragment.node.selection_set, ), }; let type_condition = type_condition.map(|condition| condition.node.on.node.as_str()); let introspection_type_name = root.introspection_type_name(); let applies_concrete_object = type_condition.map_or(false, |condition| { introspection_type_name == condition || ctx .schema_env .registry .implements .get(&*introspection_type_name) .map_or(false, |interfaces| interfaces.contains(condition)) }); if applies_concrete_object { root.collect_all_fields(&ctx.with_selection_set(selection_set), self)?; } else if type_condition.map_or(true, |condition| T::type_name() == condition) { // The fragment applies to an interface type. self.add_set(&ctx.with_selection_set(selection_set), root)?; } } } } Ok(()) } }