-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support relay-style pagination, apply on uses & invitations (#1106
- Loading branch information
Showing
14 changed files
with
984 additions
and
30 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
pub mod extract; | ||
pub mod relay; | ||
pub mod response; | ||
|
||
use std::future; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
use juniper::{ | ||
marker::IsOutputType, meta::MetaType, Arguments, ExecutionResult, Executor, GraphQLType, | ||
GraphQLValue, GraphQLValueAsync, Registry, ScalarValue, | ||
}; | ||
|
||
use crate::relay::{edge::Edge, page_info::PageInfo, NodeType}; | ||
|
||
/// Connection type | ||
/// | ||
/// Connection is the result of a query for `relay::query` or `relay::query_async` | ||
pub struct Connection<Node> { | ||
/// All edges of the current page. | ||
pub edges: Vec<Edge<Node>>, | ||
pub page_info: PageInfo, | ||
} | ||
|
||
impl<Node> Connection<Node> | ||
where | ||
Node: NodeType, | ||
{ | ||
/// Returns a relay relay with no elements. | ||
pub fn empty() -> Self { | ||
Self { | ||
edges: Vec::new(), | ||
page_info: PageInfo::default(), | ||
} | ||
} | ||
|
||
pub fn build_connection(nodes: Vec<Node>, first: Option<usize>, last: Option<usize>) -> Self { | ||
let has_next_page = first.map_or(false, |first| nodes.len() > first); | ||
let has_previous_page = last.map_or(false, |last| nodes.len() > last); | ||
let len = nodes.len(); | ||
|
||
let edges: Vec<_> = if let Some(last) = last { | ||
nodes | ||
.into_iter() | ||
.rev() | ||
.take(last) | ||
.rev() | ||
.map(|node| { | ||
let cursor = node.cursor(); | ||
Edge::new(cursor.to_string(), node) | ||
}) | ||
.collect() | ||
} else { | ||
nodes | ||
.into_iter() | ||
.take(first.unwrap_or(len)) | ||
.map(|node| { | ||
let cursor = node.cursor(); | ||
Edge::new(cursor.to_string(), node) | ||
}) | ||
.collect() | ||
}; | ||
|
||
Connection { | ||
page_info: PageInfo { | ||
has_previous_page, | ||
has_next_page, | ||
start_cursor: edges.first().map(|edge| edge.cursor.clone()), | ||
end_cursor: edges.last().map(|edge| edge.cursor.clone()), | ||
}, | ||
edges, | ||
} | ||
} | ||
} | ||
|
||
impl<Node, S> GraphQLType<S> for Connection<Node> | ||
where | ||
Node: NodeType + GraphQLType<S>, | ||
Node::Context: juniper::Context, | ||
S: ScalarValue, | ||
{ | ||
fn name(_info: &Self::TypeInfo) -> Option<&str> { | ||
Some(Node::connection_type_name()) | ||
} | ||
|
||
fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> | ||
where | ||
S: 'r, | ||
{ | ||
let fields = [ | ||
registry.field::<&Vec<Edge<Node>>>("edges", info), | ||
registry.field::<&PageInfo>("pageInfo", &()), | ||
]; | ||
registry | ||
.build_object_type::<Self>(info, &fields) | ||
.into_meta() | ||
} | ||
} | ||
|
||
impl<Node, S> GraphQLValue<S> for Connection<Node> | ||
where | ||
Node: NodeType + GraphQLType<S>, | ||
Node::Context: juniper::Context, | ||
S: ScalarValue, | ||
{ | ||
type Context = Node::Context; | ||
type TypeInfo = <Node as GraphQLValue<S>>::TypeInfo; | ||
|
||
fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { | ||
<Self as GraphQLType<S>>::name(info) | ||
} | ||
|
||
fn resolve_field( | ||
&self, | ||
info: &Self::TypeInfo, | ||
field_name: &str, | ||
_arguments: &Arguments<S>, | ||
executor: &Executor<Self::Context, S>, | ||
) -> ExecutionResult<S> { | ||
match field_name { | ||
"edges" => executor.resolve_with_ctx(info, &self.edges), | ||
"pageInfo" => executor.resolve_with_ctx(&(), &self.page_info), | ||
_ => panic!("Field {} not found on type ConnectionEdge", field_name), | ||
} | ||
} | ||
|
||
fn concrete_type_name(&self, _context: &Self::Context, info: &Self::TypeInfo) -> String { | ||
self.type_name(info).unwrap_or("Connection").to_string() | ||
} | ||
} | ||
|
||
impl<Node, S> GraphQLValueAsync<S> for Connection<Node> | ||
where | ||
Node: NodeType + GraphQLType<S> + GraphQLValueAsync<S> + Send + Sync, | ||
Node::TypeInfo: Sync, | ||
Node::Context: juniper::Context + Sync, | ||
S: ScalarValue + Send + Sync, | ||
{ | ||
fn resolve_field_async<'a>( | ||
&'a self, | ||
info: &'a Self::TypeInfo, | ||
field_name: &'a str, | ||
_arguments: &'a Arguments<S>, | ||
executor: &'a Executor<Self::Context, S>, | ||
) -> juniper::BoxFuture<'a, ExecutionResult<S>> { | ||
let f = async move { | ||
match field_name { | ||
"edges" => executor.resolve_with_ctx_async(info, &self.edges).await, | ||
"pageInfo" => executor.resolve_with_ctx(&(), &self.page_info), | ||
_ => panic!("Field {} not found on type ConnectionEdge", field_name), | ||
} | ||
}; | ||
use ::juniper::futures::future; | ||
future::FutureExt::boxed(f) | ||
} | ||
} | ||
|
||
impl<Node, S> IsOutputType<S> for Connection<Node> | ||
where | ||
Node: GraphQLType<S>, | ||
S: ScalarValue, | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
use juniper::{ | ||
marker::IsOutputType, meta::MetaType, Arguments, BoxFuture, ExecutionResult, Executor, | ||
GraphQLType, GraphQLValue, GraphQLValueAsync, Registry, ScalarValue, | ||
}; | ||
|
||
use crate::relay::NodeType; | ||
|
||
/// An edge in a relay. | ||
pub struct Edge<Node> { | ||
pub cursor: String, | ||
pub node: Node, | ||
} | ||
|
||
impl<Node> Edge<Node> { | ||
/// Create a new edge. | ||
#[inline] | ||
pub fn new(cursor: String, node: Node) -> Self { | ||
Self { cursor, node } | ||
} | ||
} | ||
|
||
impl<Node, S> GraphQLType<S> for Edge<Node> | ||
where | ||
Node: NodeType + GraphQLType<S>, | ||
Node::Context: juniper::Context, | ||
S: ScalarValue, | ||
{ | ||
fn name(_info: &Self::TypeInfo) -> Option<&str> { | ||
Some(Node::edge_type_name()) | ||
} | ||
|
||
fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S> | ||
where | ||
S: 'r, | ||
{ | ||
let fields = [ | ||
registry.field::<&Node>("node", info), | ||
registry.field::<&String>("cursor", &()), | ||
]; | ||
registry | ||
.build_object_type::<Self>(info, &fields) | ||
.into_meta() | ||
} | ||
} | ||
|
||
impl<Node, S> GraphQLValue<S> for Edge<Node> | ||
where | ||
Node: NodeType + GraphQLType<S>, | ||
Node::Context: juniper::Context, | ||
S: ScalarValue, | ||
{ | ||
type Context = Node::Context; | ||
type TypeInfo = <Node as GraphQLValue<S>>::TypeInfo; | ||
|
||
fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> { | ||
<Self as GraphQLType<S>>::name(info) | ||
} | ||
|
||
fn resolve_field( | ||
&self, | ||
info: &Self::TypeInfo, | ||
field_name: &str, | ||
_arguments: &Arguments<S>, | ||
executor: &Executor<Self::Context, S>, | ||
) -> ExecutionResult<S> { | ||
match field_name { | ||
"node" => executor.resolve_with_ctx(info, &self.node), | ||
"cursor" => executor.resolve_with_ctx(&(), &self.cursor), | ||
_ => panic!("Field {} not found on type ConnectionEdge", field_name), | ||
} | ||
} | ||
|
||
fn concrete_type_name(&self, _context: &Self::Context, info: &Self::TypeInfo) -> String { | ||
self.type_name(info).unwrap_or("ConnectionEdge").to_string() | ||
} | ||
} | ||
|
||
impl<Node, S> GraphQLValueAsync<S> for Edge<Node> | ||
where | ||
Node: NodeType + GraphQLType<S> + GraphQLValueAsync<S> + Send + Sync, | ||
Node::TypeInfo: Sync, | ||
Node::Context: juniper::Context + Sync, | ||
S: ScalarValue + Send + Sync, | ||
{ | ||
fn resolve_field_async<'a>( | ||
&'a self, | ||
info: &'a Self::TypeInfo, | ||
field_name: &'a str, | ||
_arguments: &'a Arguments<S>, | ||
executor: &'a Executor<Self::Context, S>, | ||
) -> BoxFuture<'a, ExecutionResult<S>> { | ||
let f = async move { | ||
match field_name { | ||
"node" => executor.resolve_with_ctx_async(info, &self.node).await, | ||
"cursor" => executor.resolve_with_ctx(&(), &self.cursor), | ||
_ => panic!("Field {} not found on type RelayConnectionEdge", field_name), | ||
} | ||
}; | ||
use ::juniper::futures::future; | ||
future::FutureExt::boxed(f) | ||
} | ||
} | ||
|
||
impl<Node, S> IsOutputType<S> for Edge<Node> | ||
where | ||
Node: GraphQLType<S>, | ||
S: ScalarValue, | ||
{ | ||
} |
Oops, something went wrong.