//! Types for Relay-compliant server mod connection_type; mod cursor; mod edge; mod page_info; use std::{fmt::Display, future::Future}; pub use connection_type::Connection; pub use cursor::CursorType; pub use edge::Edge; pub use page_info::PageInfo; use crate::{Error, ObjectType, OutputType, Result, SimpleObject}; /// Empty additional fields #[derive(SimpleObject)] #[graphql(internal, fake)] pub struct EmptyFields; /// Used to specify the edge name. pub trait EdgeNameType: Send + Sync { /// Returns the edge type name. fn type_name() -> String; } /// Name the edge type by default with the default format. pub struct DefaultEdgeName; impl EdgeNameType for DefaultEdgeName { fn type_name() -> String { format!("{}Edge", T::type_name()) } } /// Used to specify the connection name. pub trait ConnectionNameType: Send + Sync { /// Returns the connection type name. fn type_name() -> String; } /// Name the connection type by default with the default format. pub struct DefaultConnectionName; impl ConnectionNameType for DefaultConnectionName { fn type_name() -> String { format!("{}Connection", T::type_name()) } } /// Parses the parameters and executes the query. /// /// # Examples /// /// ```rust /// use std::borrow::Cow; /// /// use async_graphql::*; /// use async_graphql::types::connection::*; /// /// struct Query; /// /// struct Numbers; /// /// #[derive(SimpleObject)] /// struct Diff { /// diff: i32, /// } /// /// #[Object] /// impl Query { /// async fn numbers(&self, /// after: Option, /// before: Option, /// first: Option, /// last: Option /// ) -> Result> { /// query(after, before, first, last, |after, before, first, last| async move { /// let mut start = after.map(|after| after + 1).unwrap_or(0); /// let mut end = before.unwrap_or(10000); /// if let Some(first) = first { /// end = (start + first).min(end); /// } /// if let Some(last) = last { /// start = if last > end - start { /// end /// } else { /// end - last /// }; /// } /// let mut connection = Connection::new(start > 0, end < 10000); /// connection.edges.extend( /// (start..end).into_iter().map(|n| /// Edge::with_additional_fields(n, n as i32, Diff{ diff: (10000 - n) as i32 })), /// ); /// Ok::<_, Error>(connection) /// }).await /// } /// } /// /// # tokio::runtime::Runtime::new().unwrap().block_on(async { /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); /// /// assert_eq!(schema.execute("{ numbers(first: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({ /// "numbers": { /// "edges": [ /// {"node": 0, "diff": 10000}, /// {"node": 1, "diff": 9999}, /// ] /// }, /// })); /// /// assert_eq!(schema.execute("{ numbers(last: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({ /// "numbers": { /// "edges": [ /// {"node": 9998, "diff": 2}, /// {"node": 9999, "diff": 1}, /// ] /// }, /// })); /// # }); /// ``` /// /// # Custom connection and edge type names /// /// ``` /// use async_graphql::{connection::*, *}; /// /// #[derive(SimpleObject)] /// struct MyObj { /// a: i32, /// b: String, /// } /// /// // Use to custom connection name /// struct MyConnectionName; /// /// impl ConnectionNameType for MyConnectionName { /// fn type_name() -> String { /// "MyConnection".to_string() /// } /// } /// /// // Use to custom edge name /// struct MyEdgeName; /// /// impl EdgeNameType for MyEdgeName { /// fn type_name() -> String { /// "MyEdge".to_string() /// } /// } /// /// struct Query; /// /// #[Object] /// impl Query { /// async fn numbers( /// &self, /// after: Option, /// before: Option, /// first: Option, /// last: Option, /// ) -> Connection { /// let mut connection = Connection::new(false, false); /// connection.edges.push(Edge::new(1, MyObj { a: 100, b: "abc".to_string() })); /// connection /// } /// } /// /// # tokio::runtime::Runtime::new().unwrap().block_on(async { /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); /// /// let query = r#"{ /// numbers(first: 2) { /// __typename /// edges { __typename node { a b } } /// } /// }"#; /// let data = schema.execute(query).await.into_result().unwrap().data; /// assert_eq!(data, value!({ /// "numbers": { /// "__typename": "MyConnection", /// "edges": [ /// {"__typename": "MyEdge", "node": { "a": 100, "b": "abc" }}, /// ] /// }, /// })); /// # }); /// ``` pub async fn query( after: Option, before: Option, first: Option, last: Option, f: F, ) -> Result> where Name: ConnectionNameType, EdgeName: EdgeNameType, Cursor: CursorType + Send + Sync, ::Error: Display + Send + Sync + 'static, Node: OutputType, ConnectionFields: ObjectType, EdgeFields: ObjectType, F: FnOnce(Option, Option, Option, Option) -> R, R: Future< Output = Result, E>, >, E: Into, { query_with(after, before, first, last, f).await } /// Parses the parameters and executes the query and return a custom /// `Connection` type. /// /// `Connection` and `Edge` have certain limitations. For example, you /// cannot customize the name of the type, so you can use this function to /// execute the query and return a customized `Connection` type. /// /// # Examples /// /// ```rust /// /// use async_graphql::*; /// use async_graphql::types::connection::*; /// /// #[derive(SimpleObject)] /// struct MyEdge { /// cursor: usize, /// node: i32, /// diff: i32, /// } /// /// #[derive(SimpleObject)] /// struct MyConnection { /// edges: Vec, /// page_info: PageInfo, /// } /// /// struct Query; /// /// #[Object] /// impl Query { /// async fn numbers(&self, /// after: Option, /// before: Option, /// first: Option, /// last: Option /// ) -> Result { /// query_with(after, before, first, last, |after, before, first, last| async move { /// let mut start = after.map(|after| after + 1).unwrap_or(0); /// let mut end = before.unwrap_or(10000); /// if let Some(first) = first { /// end = (start + first).min(end); /// } /// if let Some(last) = last { /// start = if last > end - start { /// end /// } else { /// end - last /// }; /// } /// let connection = MyConnection { /// edges: (start..end).into_iter().map(|n| MyEdge { /// cursor: n, /// node: n as i32, /// diff: (10000 - n) as i32, /// }).collect(), /// page_info: PageInfo { /// has_previous_page: start > 0, /// has_next_page: end < 10000, /// start_cursor: Some(start.encode_cursor()), /// end_cursor: Some(end.encode_cursor()), /// }, /// }; /// Ok::<_, Error>(connection) /// }).await /// } /// } /// /// # tokio::runtime::Runtime::new().unwrap().block_on(async { /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); /// /// assert_eq!(schema.execute("{ numbers(first: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({ /// "numbers": { /// "edges": [ /// {"node": 0, "diff": 10000}, /// {"node": 1, "diff": 9999}, /// ] /// }, /// })); /// /// assert_eq!(schema.execute("{ numbers(last: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({ /// "numbers": { /// "edges": [ /// {"node": 9998, "diff": 2}, /// {"node": 9999, "diff": 1}, /// ] /// }, /// })); /// # }); /// ``` pub async fn query_with( after: Option, before: Option, first: Option, last: Option, f: F, ) -> Result where Cursor: CursorType + Send + Sync, ::Error: Display + Send + Sync + 'static, F: FnOnce(Option, Option, Option, Option) -> R, R: Future>, E: Into, { if first.is_some() && last.is_some() { return Err("The \"first\" and \"last\" parameters cannot exist at the same time".into()); } let first = match first { Some(first) if first < 0 => { return Err("The \"first\" parameter must be a non-negative number".into()); } Some(first) => Some(first as usize), None => None, }; let last = match last { Some(last) if last < 0 => { return Err("The \"last\" parameter must be a non-negative number".into()); } Some(last) => Some(last as usize), None => None, }; let before = match before { Some(before) => Some(Cursor::decode_cursor(&before)?), None => None, }; let after = match after { Some(after) => Some(Cursor::decode_cursor(&after)?), None => None, }; f(after, before, first, last).await.map_err(Into::into) }