use std::borrow::Cow; use futures_util::stream::{Stream, StreamExt, TryStreamExt}; use indexmap::map::IndexMap; use crate::connection::edge::Edge; use crate::connection::page_info::PageInfo; use crate::parser::types::Field; use crate::resolver_utils::{resolve_container, ContainerType}; use crate::types::connection::{CursorType, EmptyFields}; use crate::{ registry, Context, ContextSelectionSet, ObjectType, OutputType, Positioned, Result, ServerResult, Type, Value, }; /// Connection type /// /// Connection is the result of a query for `connection::query`. pub struct Connection { /// All edges of the current page. edges: Vec>, additional_fields: EC, has_previous_page: bool, has_next_page: bool, } impl Connection { /// Create a new connection. pub fn new(has_previous_page: bool, has_next_page: bool) -> Self { Connection { additional_fields: EmptyFields, has_previous_page, has_next_page, edges: Vec::new(), } } } impl Connection { /// Create a new connection, it can have some additional fields. pub fn with_additional_fields( has_previous_page: bool, has_next_page: bool, additional_fields: EC, ) -> Self { Connection { additional_fields, has_previous_page, has_next_page, edges: Vec::new(), } } } impl Connection { /// Convert the edge type and return a new `Connection`. pub fn map(self, mut f: F) -> Connection where F: FnMut(Edge) -> Edge, { let mut new_edges = Vec::with_capacity(self.edges.len()); for edge in self.edges { new_edges.push(f(edge)); } Connection { edges: new_edges, additional_fields: self.additional_fields, has_previous_page: self.has_previous_page, has_next_page: self.has_next_page, } } /// Convert the node type and return a new `Connection`. pub fn map_node(self, mut f: F) -> Connection where F: FnMut(T) -> T2, { self.map(|edge| Edge { cursor: edge.cursor, node: f(edge.node), additional_fields: edge.additional_fields, }) } /// Append edges with `IntoIterator>` pub fn append(&mut self, iter: I) where I: IntoIterator>, { self.edges.extend(iter); } /// Append edges with `IntoIterator>` pub fn try_append(&mut self, iter: I) -> Result<()> where I: IntoIterator>>, { for edge in iter { self.edges.push(edge?); } Ok(()) } /// Append edges with `Stream>>` pub async fn append_stream(&mut self, stream: S) where S: Stream> + Unpin, { self.edges.extend(stream.collect::>().await); } /// Append edges with `Stream>>` pub async fn try_append_stream(&mut self, stream: S) -> Result<()> where S: Stream>> + Unpin, { self.edges.extend(stream.try_collect::>().await?); Ok(()) } } impl Type for Connection where C: CursorType, T: OutputType + Send + Sync, EC: ObjectType + Sync + Send, EE: ObjectType + Sync + Send, { fn type_name() -> Cow<'static, str> { Cow::Owned(format!("{}Connection", T::type_name())) } fn create_type_info(registry: &mut registry::Registry) -> String { registry.create_type::(|registry| { EC::create_type_info(registry); let additional_fields = if let Some(registry::MetaType::Object { fields, .. }) = registry.types.remove(EC::type_name().as_ref()) { fields } else { unreachable!() }; registry::MetaType::Object { name: Self::type_name().to_string(), description: None, fields: { let mut fields = IndexMap::new(); fields.insert( "pageInfo".to_string(), registry::MetaField { name: "pageInfo".to_string(), description: Some("Information to aid in pagination."), args: Default::default(), ty: PageInfo::create_type_info(registry), deprecation: None, cache_control: Default::default(), external: false, requires: None, provides: None, }, ); fields.insert( "edges".to_string(), registry::MetaField { name: "edges".to_string(), description: Some("A list of edges."), args: Default::default(), ty: >>> as Type>::create_type_info( registry, ), deprecation: None, cache_control: Default::default(), external: false, requires: None, provides: None, }, ); fields.extend(additional_fields); fields }, cache_control: Default::default(), extends: false, keys: None, } }) } } #[async_trait::async_trait] impl ContainerType for Connection where C: CursorType + Send + Sync, T: OutputType + Send + Sync, EC: ObjectType + Sync + Send, EE: ObjectType + Sync + Send, { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult> { if ctx.item.node.name.node == "pageInfo" { let page_info = PageInfo { has_previous_page: self.has_previous_page, has_next_page: self.has_next_page, start_cursor: self.edges.first().map(|edge| edge.cursor.encode_cursor()), end_cursor: self.edges.last().map(|edge| edge.cursor.encode_cursor()), }; let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); return OutputType::resolve(&page_info, &ctx_obj, ctx.item) .await .map(Some); } else if ctx.item.node.name.node == "edges" { let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); return OutputType::resolve(&self.edges, &ctx_obj, ctx.item) .await .map(Some); } self.additional_fields.resolve_field(ctx).await } } #[async_trait::async_trait] impl OutputType for Connection where C: CursorType + Send + Sync, T: OutputType + Send + Sync, EC: ObjectType + Sync + Send, EE: ObjectType + Sync + Send, { async fn resolve( &self, ctx: &ContextSelectionSet<'_>, _field: &Positioned, ) -> ServerResult { resolve_container(ctx, self).await } } impl ObjectType for Connection where C: CursorType + Send + Sync, T: OutputType + Send + Sync, EC: ObjectType + Sync + Send, EE: ObjectType + Sync + Send, { }