async-graphql/src/types/connection/mod.rs

350 lines
11 KiB
Rust
Raw Normal View History

2020-03-21 01:32:13 +00:00
mod connection_type;
mod cursor;
2020-03-19 09:20:12 +00:00
mod edge;
mod page_info;
mod slice;
2020-05-20 16:15:47 +00:00
mod stream;
2020-03-19 09:20:12 +00:00
use crate::{Context, FieldResult, ObjectType};
2020-03-19 09:20:12 +00:00
2020-03-21 01:32:13 +00:00
pub use connection_type::Connection;
2020-05-10 13:49:29 +00:00
pub use cursor::Cursor;
pub use page_info::PageInfo;
2020-03-19 09:20:12 +00:00
2020-03-20 03:56:08 +00:00
/// Connection query operation
2020-05-20 16:15:47 +00:00
#[derive(Debug, Clone)]
2020-05-05 07:22:01 +00:00
pub enum QueryOperation {
/// Return all results
None,
/// Return all results after the cursor
After {
2020-03-20 03:56:08 +00:00
/// After this cursor
2020-05-05 07:22:01 +00:00
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
2020-03-19 09:20:12 +00:00
limit: usize,
2020-05-05 07:22:01 +00:00
/// After this cursor
after: Cursor,
2020-03-19 09:20:12 +00:00
},
2020-05-05 07:22:01 +00:00
/// 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,
2020-03-20 03:56:08 +00:00
/// Before this cursor
2020-05-05 07:22:01 +00:00
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
2020-03-19 09:20:12 +00:00
limit: usize,
2020-05-05 07:22:01 +00:00
/// After this cursor
after: Cursor,
/// But before this cursor
before: Cursor,
2020-03-19 09:20:12 +00:00
},
2020-05-05 07:22:01 +00:00
/// 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,
2020-03-19 09:20:12 +00:00
}
2020-03-20 03:56:08 +00:00
/// Empty edge extension object
2020-04-17 03:06:33 +00:00
#[async_graphql_derive::SimpleObject(internal)]
2020-03-19 09:20:12 +00:00
pub struct EmptyEdgeFields;
2020-05-05 07:22:01 +00:00
// Temporary struct for to store values for pattern matching
struct Pagination {
after: Option<Cursor>,
before: Option<Cursor>,
first: Option<i32>,
last: Option<i32>,
}
2020-03-20 03:56:08 +00:00
/// Data source of GraphQL Cursor Connections type
2020-03-19 09:20:12 +00:00
///
/// `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;
///
2020-04-17 03:06:33 +00:00
/// #[SimpleObject]
/// struct DiffFields {
/// diff: i32,
2020-03-19 09:20:12 +00:00
/// }
///
/// struct Numbers;
///
2020-04-17 03:06:33 +00:00
/// #[DataSource]
2020-03-19 09:20:12 +00:00
/// impl DataSource for Numbers {
/// type Element = i32;
2020-03-20 03:56:08 +00:00
/// type EdgeFieldsObj = DiffFields;
2020-03-19 09:20:12 +00:00
///
/// async fn query_operation(&mut self, ctx: &Context<'_>, operation: &QueryOperation) -> FieldResult<Connection<Self::Element, Self::EdgeFieldsObj>> {
2020-03-19 09:20:12 +00:00
/// let (start, end) = match operation {
2020-05-05 07:22:01 +00:00
/// 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()
2020-03-19 09:20:12 +00:00
/// .and_then(|data| data.as_slice().read_i32::<BE>().ok())
/// .map(|idx| idx + 1)
/// .unwrap_or(0);
/// let end = start + *limit as i32;
/// (start, end)
/// }
2020-05-05 07:22:01 +00:00
/// QueryOperation::LastBefore {before, limit} => {
/// let end = base64::decode(before.to_string())
/// .ok()
2020-03-19 09:20:12 +00:00
/// .and_then(|data| data.as_slice().read_i32::<BE>().ok())
/// .unwrap_or(0);
/// let start = end - *limit as i32;
/// (start, end)
/// }
2020-05-05 07:22:01 +00:00
/// // You should handle all cases instead of using a default like this
/// _ => (0, 10)
2020-03-19 09:20:12 +00:00
/// };
///
/// let nodes = (start..end).into_iter().map(|n| (base64::encode(n.to_be_bytes()).into(), DiffFields {diff: n - 1000}, n)).collect();
2020-03-19 09:20:12 +00:00
/// Ok(Connection::new(None, true, true, nodes))
/// }
/// }
///
/// #[Object]
/// impl QueryRoot {
/// async fn numbers(&self, ctx: &Context<'_>,
2020-05-05 07:22:01 +00:00
/// after: Option<Cursor>,
/// before: Option<Cursor>,
2020-03-19 09:20:12 +00:00
/// first: Option<i32>,
/// last: Option<i32>
/// ) -> FieldResult<Connection<i32, DiffFields>> {
2020-03-19 09:20:12 +00:00
/// Numbers.query(ctx, after, before, first, last).await
/// }
/// }
///
/// #[async_std::main]
/// async fn main() {
/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
///
2020-04-02 04:53:53 +00:00
/// assert_eq!(schema.execute("{ numbers(first: 2) { edges { node } } }").await.unwrap().data, serde_json::json!({
2020-03-19 09:20:12 +00:00
/// "numbers": {
/// "edges": [
/// {"node": 0},
/// {"node": 1}
/// ]
/// },
/// }));
///
2020-04-02 04:53:53 +00:00
/// assert_eq!(schema.execute("{ numbers(last: 2) { edges { node diff } } }").await.unwrap().data, serde_json::json!({
2020-03-19 09:20:12 +00:00
/// "numbers": {
/// "edges": [
/// {"node": -2, "diff": -1002},
/// {"node": -1, "diff": -1001}
/// ]
/// },
/// }));
/// }
/// ```
#[async_trait::async_trait]
2020-05-21 10:51:01 +00:00
pub trait DataSource: Send {
2020-03-20 03:56:08 +00:00
/// Record type
2020-03-19 09:20:12 +00:00
type Element;
2020-03-20 03:56:08 +00:00
/// Fields for Edge
///
/// Is a type that implements `ObjectType` and can be defined by the procedure macro `#[Object]`.
2020-05-21 10:51:01 +00:00
type EdgeFieldsObj: ObjectType + Send;
2020-03-20 03:56:08 +00:00
/// Execute the query.
2020-03-19 09:20:12 +00:00
async fn query(
2020-05-20 16:15:47 +00:00
&mut self,
2020-05-07 00:04:26 +00:00
ctx: &Context<'_>,
2020-05-05 07:22:01 +00:00
after: Option<Cursor>,
before: Option<Cursor>,
2020-03-19 09:20:12 +00:00
first: Option<i32>,
last: Option<i32>,
) -> FieldResult<Connection<Self::Element, Self::EdgeFieldsObj>> {
2020-05-05 07:22:01 +00:00
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),
2020-03-19 09:20:12 +00:00
after: Some(after),
2020-05-05 07:22:01 +00:00
} => 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,
2020-03-19 09:20:12 +00:00
before: Some(before),
after: None,
2020-05-05 07:22:01 +00:00
} => 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),
2020-03-19 09:20:12 +00:00
before: None,
after: None,
2020-05-05 07:22:01 +00:00
} => 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,
},
2020-03-19 09:20:12 +00:00
};
2020-05-07 00:04:26 +00:00
self.query_operation(ctx, &operation).await
2020-03-19 09:20:12 +00:00
}
2020-03-20 03:56:08 +00:00
/// Parses the parameters and executes the queryUsually you just need to implement this method.
2020-03-19 09:20:12 +00:00
async fn query_operation(
2020-05-20 16:15:47 +00:00
&mut self,
2020-05-07 00:04:26 +00:00
ctx: &Context<'_>,
2020-05-05 07:22:01 +00:00
operation: &QueryOperation,
) -> FieldResult<Connection<Self::Element, Self::EdgeFieldsObj>>;
2020-03-19 09:20:12 +00:00
}