mod connection_type; mod cursor; mod edge; mod page_info; mod slice; mod stream; use crate::{Context, FieldResult, ObjectType}; pub use connection_type::Connection; pub use cursor::Cursor; pub use page_info::PageInfo; pub use stream::StreamDataSource; /// Connection query operation #[derive(Debug, Clone)] pub enum QueryOperation { /// Return all results None, /// Return all results after the cursor After { /// After this cursor after: Cursor, }, /// Return all results before the cursor Before { /// Before this cursor before: Cursor, }, /// Return all results between the cursors Between { /// After this cursor after: Cursor, /// But before this cursor before: Cursor, }, /// Return the amount of results specified by `limit`, starting from the beginning First { /// The maximum amount of results to return limit: usize, }, /// Return the amount of results specified by `limit`, starting after the cursor FirstAfter { /// The maximum amount of results to return limit: usize, /// After this cursor after: Cursor, }, /// Return the amount of results specified by `limit`, starting from the beginning but ending before the cursor FirstBefore { /// The maximum amount of results to return limit: usize, /// Before this cursor before: Cursor, }, /// Return the amount of results specified by `limit`, but between the cursors. Limit includes beginning results. FirstBetween { /// The maximum amount of results to return limit: usize, /// After this cursor after: Cursor, /// But before this cursor before: Cursor, }, /// Return the amount of results specified by `limit`, but before the end Last { /// The maximum amount of results to return limit: usize, }, /// Return the amount of results specified by `limit`, but before the end. Must not include anything before the cursor. LastAfter { /// The maximum amount of results to return limit: usize, /// After this cursor after: Cursor, }, /// Return the amount of results specified by `limit`, but before the cursor LastBefore { /// The maximum amount of results to return limit: usize, /// Before this cursor before: Cursor, }, /// Return the amount of results specified by `limit`, but between the cursors. Limit includes ending results. LastBetween { /// The maximum amount of results to return limit: usize, /// After this cursor after: Cursor, /// But before this cursor before: Cursor, }, /// An invalid query was made. For example: sending `first` and `last` in the same query Invalid, } /// Empty edge extension object #[async_graphql_derive::SimpleObject(internal)] pub struct EmptyEdgeFields; // Temporary struct for to store values for pattern matching struct Pagination { after: Option, before: Option, first: Option, last: Option, } /// Data source of GraphQL Cursor Connections type /// /// `Edge` is an extension object type that extends the edge fields, If you don't need it, you can use `EmptyEdgeFields`. /// /// # References /// (GraphQL Cursor Connections Specification)[https://facebook.github.io/relay/graphql/connections.htm] /// /// # Examples /// /// ```rust /// use async_graphql::*; /// use byteorder::{ReadBytesExt, BE}; /// /// struct QueryRoot; /// /// #[SimpleObject] /// struct DiffFields { /// diff: i32, /// } /// /// struct Numbers; /// /// #[DataSource] /// impl DataSource for Numbers { /// type Element = i32; /// type EdgeFieldsObj = DiffFields; /// /// async fn query_operation(&mut self, ctx: &Context<'_>, operation: &QueryOperation) -> FieldResult> { /// let (start, end) = match operation { /// QueryOperation::First {limit} => { /// let start = 0; /// let end = start + *limit as i32; /// (start, end) /// } /// QueryOperation::Last {limit} => { /// let end = 0; /// let start = end - *limit as i32; /// (start, end) /// } /// QueryOperation::FirstAfter {after, limit} => { /// let start = base64::decode(after.to_string()) /// .ok() /// .and_then(|data| data.as_slice().read_i32::().ok()) /// .map(|idx| idx + 1) /// .unwrap_or(0); /// let end = start + *limit as i32; /// (start, end) /// } /// QueryOperation::LastBefore {before, limit} => { /// let end = base64::decode(before.to_string()) /// .ok() /// .and_then(|data| data.as_slice().read_i32::().ok()) /// .unwrap_or(0); /// let start = end - *limit as i32; /// (start, end) /// } /// // You should handle all cases instead of using a default like this /// _ => (0, 10) /// }; /// /// let nodes = (start..end).into_iter().map(|n| (base64::encode(n.to_be_bytes()).into(), DiffFields {diff: n - 1000}, n)).collect(); /// Ok(Connection::new(None, true, true, nodes)) /// } /// } /// /// #[Object] /// impl QueryRoot { /// async fn numbers(&self, ctx: &Context<'_>, /// after: Option, /// before: Option, /// first: Option, /// last: Option /// ) -> FieldResult> { /// Numbers.query(ctx, after, before, first, last).await /// } /// } /// /// #[async_std::main] /// async fn main() { /// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); /// /// assert_eq!(schema.execute("{ numbers(first: 2) { edges { node } } }").await.unwrap().data, serde_json::json!({ /// "numbers": { /// "edges": [ /// {"node": 0}, /// {"node": 1} /// ] /// }, /// })); /// /// assert_eq!(schema.execute("{ numbers(last: 2) { edges { node diff } } }").await.unwrap().data, serde_json::json!({ /// "numbers": { /// "edges": [ /// {"node": -2, "diff": -1002}, /// {"node": -1, "diff": -1001} /// ] /// }, /// })); /// } /// ``` #[async_trait::async_trait] pub trait DataSource: Sync + Send { /// Record type type Element; /// Fields for Edge /// /// Is a type that implements `ObjectType` and can be defined by the procedure macro `#[Object]`. type EdgeFieldsObj: ObjectType + Send + Sync; /// Execute the query. async fn query( &mut self, ctx: &Context<'_>, after: Option, before: Option, first: Option, last: Option, ) -> FieldResult> { let pagination = Pagination { first, last, before, after, }; let operation = match pagination { // This is technically allowed according to the Relay Spec, but highly discouraged Pagination { first: Some(_), last: Some(_), before: _, after: _, } => QueryOperation::Invalid, Pagination { first: None, last: None, before: None, after: None, } => QueryOperation::None, Pagination { first: None, last: None, before: Some(before), after: None, } => QueryOperation::Before { before }, Pagination { first: None, last: None, before: None, after: Some(after), } => QueryOperation::After { after }, Pagination { first: None, last: None, before: Some(before), after: Some(after), } => QueryOperation::Between { after, before }, Pagination { first: Some(limit), last: None, before: None, after: None, } => QueryOperation::First { limit: limit.max(0) as usize, }, Pagination { first: Some(limit), last: None, before: Some(before), after: None, } => QueryOperation::FirstBefore { limit: limit.max(0) as usize, before, }, Pagination { first: Some(limit), last: None, before: None, after: Some(after), } => QueryOperation::FirstAfter { limit: limit.max(0) as usize, after, }, Pagination { first: Some(limit), last: None, before: Some(before), after: Some(after), } => QueryOperation::FirstBetween { limit: limit.max(0) as usize, after, before, }, Pagination { first: None, last: Some(limit), before: None, after: None, } => QueryOperation::Last { limit: limit.max(0) as usize, }, Pagination { first: None, last: Some(limit), before: Some(before), after: None, } => QueryOperation::LastBefore { limit: limit.max(0) as usize, before, }, Pagination { first: None, last: Some(limit), before: None, after: Some(after), } => QueryOperation::LastAfter { limit: limit.max(0) as usize, after, }, Pagination { first: None, last: Some(limit), before: Some(before), after: Some(after), } => QueryOperation::LastBetween { limit: limit.max(0) as usize, after, before, }, }; self.query_operation(ctx, &operation).await } /// Parses the parameters and executes the query,Usually you just need to implement this method. async fn query_operation( &mut self, ctx: &Context<'_>, operation: &QueryOperation, ) -> FieldResult>; }