mod connection_type; mod edge; mod page_info; mod slice; use crate::{Context, ObjectType, QueryError, Result}; pub use connection_type::Connection; /// Connection query operation pub enum QueryOperation<'a> { /// Forward query Forward { /// After this cursor after: Option<&'a str>, /// How many records did this query return limit: usize, }, /// Backward query Backward { /// Before this cursor before: Option<&'a str>, /// How many records did this query return limit: usize, }, } /// Empty edge extension object pub struct EmptyEdgeFields; #[async_graphql_derive::Object(internal)] impl EmptyEdgeFields {} /// 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; /// /// struct DiffFields(i32); /// /// #[Object] /// impl DiffFields { /// #[field] /// async fn diff(&self) -> i32 { /// self.0 /// } /// } /// /// struct Numbers; /// /// #[async_trait::async_trait] /// impl DataSource for Numbers { /// type Element = i32; /// type EdgeFieldsObj = DiffFields; /// /// async fn query_operation(&self, operation: &QueryOperation<'_>) -> Result> { /// let (start, end) = match operation { /// QueryOperation::Forward {after, limit} => { /// let start = after.and_then(|after| base64::decode(after).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::Backward {before, limit} => { /// let end = before.and_then(|before| base64::decode(before).ok()) /// .and_then(|data| data.as_slice().read_i32::().ok()) /// .unwrap_or(0); /// let start = end - *limit as i32; /// (start, end) /// } /// }; /// /// let nodes = (start..end).into_iter().map(|n| (base64::encode(n.to_be_bytes()), DiffFields(n - 1000), n)).collect(); /// Ok(Connection::new(None, true, true, nodes)) /// } /// } /// /// #[Object] /// impl QueryRoot { /// #[field] /// async fn numbers(&self, ctx: &Context<'_>, /// after: Option, /// before: Option, /// first: Option, /// last: Option /// ) -> Result> { /// Numbers.query(ctx, after, before, first, last).await /// } /// } /// /// #[async_std::main] /// async fn main() { /// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); /// /// assert_eq!(serde_json::from_str::(&schema.query("{ numbers(first: 2) { edges { node } } }").execute().await.unwrap()).unwrap(), serde_json::json!({ /// "numbers": { /// "edges": [ /// {"node": 0}, /// {"node": 1} /// ] /// }, /// })); /// /// assert_eq!(serde_json::from_str::(&schema.query("{ numbers(last: 2) { edges { node diff } } }").execute().await.unwrap()).unwrap(), 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( &self, ctx: &Context<'_>, after: Option, before: Option, first: Option, last: Option, ) -> Result> { let operation = if let Some(after) = &after { QueryOperation::Forward { after: Some(after), limit: match first { Some(value) => { if value < 0 { return Err(QueryError::ArgumentMustBeNonNegative { field_name: ctx.name.clone(), } .into()); } else { value as usize } } None => 10, }, } } else if let Some(before) = &before { QueryOperation::Backward { before: Some(before), limit: match last { Some(value) => { if value < 0 { return Err(QueryError::ArgumentMustBeNonNegative { field_name: ctx.name.clone(), } .into()); } else { value as usize } } None => 10, }, } } else if let Some(first) = first { QueryOperation::Forward { after: None, limit: if first < 0 { return Err(QueryError::ArgumentMustBeNonNegative { field_name: ctx.name.clone(), } .into()); } else { first as usize }, } } else if let Some(last) = last { QueryOperation::Backward { before: None, limit: if last < 0 { return Err(QueryError::ArgumentMustBeNonNegative { field_name: ctx.name.clone(), } .into()); } else { last as usize }, } } else { QueryOperation::Forward { after: None, limit: 10, } }; self.query_operation(&operation).await } /// Parses the parameters and executes the query,Usually you just need to implement this method. async fn query_operation( &self, operation: &QueryOperation<'_>, ) -> Result>; }