2020-03-21 01:32:13 +00:00
|
|
|
|
mod connection_type;
|
2020-05-05 05:21:45 +00:00
|
|
|
|
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
|
|
|
|
|
2020-04-02 02:21:04 +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;
|
2020-05-05 22:52:04 +00:00
|
|
|
|
pub use page_info::PageInfo;
|
2020-05-20 16:15:47 +00:00
|
|
|
|
pub use stream::StreamDataSource;
|
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
|
|
|
|
///
|
2020-05-07 00:04:26 +00:00
|
|
|
|
/// async fn query_operation(&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
|
|
|
|
/// };
|
|
|
|
|
///
|
2020-05-05 05:21:45 +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>
|
2020-04-02 02:21:04 +00:00
|
|
|
|
/// ) -> 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]
|
|
|
|
|
pub trait DataSource: Sync + 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]`.
|
|
|
|
|
type EdgeFieldsObj: ObjectType + Send + Sync;
|
|
|
|
|
|
|
|
|
|
/// 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>,
|
2020-04-02 02:21:04 +00:00
|
|
|
|
) -> 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 query,Usually 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,
|
2020-04-02 02:21:04 +00:00
|
|
|
|
) -> FieldResult<Connection<Self::Element, Self::EdgeFieldsObj>>;
|
2020-03-19 09:20:12 +00:00
|
|
|
|
}
|