-
Notifications
You must be signed in to change notification settings - Fork 434
Creating a generic GraphQL structure #1051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@ClementNerma unfortunately, you cannot do things like this conceptually: #[graphql_object]
impl<T> Paginated<T> { The problem is that In our codebases we solve this problem in the following way:
/// Generic [Connection Type][1] according to
/// [GraphQL Cursor Connections Specification][0].
///
/// [0]: https://tinyurl.com/gql-relay
/// [1]: https://tinyurl.com/gql-relay#sec-Connection-Types
#[derive(Debug, SmartDefault)]
pub struct Connection<Node, Cursor> {
/// List of [`Edge`]s for this [`Connection`].
pub edges: Vec<Edge<Node, Cursor>>,
/// Indicates whether this [`Connection`] has next page.
pub has_next_page: bool,
/// Indicates whether this [`Connection`] has previous page.
pub has_previous_page: bool,
}
impl<Node, Cursor> Connection<Node, Cursor> {
/// Returns list of [`Edge`]s for this [`Connection`].
pub(crate) fn _edges(&self) -> &[Edge<Node, Cursor>] {
self.edges.as_slice()
}
/// Returns list of this [`Connection`] `Node`s.
pub(crate) fn _nodes(&self) -> Vec<&Node> {
self.edges.iter().map(|edge| &edge.node).collect()
}
/// Returns [`PageInfo`] of this [`Connection`].
pub(crate) fn _page_info(&self) -> PageInfo<'_>
where
Cursor: AsRef<str>,
{
PageInfo {
end_cursor: self.edges.last().map(|edge| edge.cursor.as_ref()),
has_next_page: self.has_next_page,
start_cursor: self.edges.first().map(|edge| edge.cursor.as_ref()),
has_previous_page: self.has_previous_page,
}
}
}
/// Generic [Edge Type] according to
/// [GraphQL Cursor Connections Specification][0].
///
/// [0]: https://tinyurl.com/gql-relay
/// [Edge Type]: https://tinyurl.com/gql-relay#sec-Edge-Types
#[derive(Clone, Debug)]
pub struct Edge<Node, Cursor> {
/// Item at the end of this [`Edge`].
pub node: Node,
/// Cursor of this [`Edge`].
pub cursor: Cursor,
}
/// [`PageInfo`][1] returned by a [Connection] according to
/// [GraphQL Cursor Connections Specification][0].
///
/// [0]: https://tinyurl.com/gql-relay
/// [1]: https://tinyurl.com/gql-relay#sec-Connection-Types.Fields.PageInfo
/// [Connection]: https://tinyurl.com/gql-relay#sec-Connection-Types
#[derive(Debug, GraphQLObject)]
pub struct PageInfo<'a> {
/// [Cursor] pointing to the last [Node] in [Connection]'s [Edges].
///
/// [Cursor]: https://tinyurl.com/gql-relay#sec-Cursor
/// [Node]: https://tinyurl.com/gql-relay#sec-Node
/// [Connection]: https://tinyurl.com/gql-relay#sec-Connection-Types
/// [Edges]: https://tinyurl.com/gql-relay#sec-Edges
pub end_cursor: Option<&'a str>,
/// Indicator whether more [Edge]s exist following the set defined by the
/// clients arguments.
///
/// If the client is paginating with `first`/`after`, then `true` is
/// returned if further [Edge]s exist, otherwise `false`.
///
/// If the client is paginating with `last`/`before`, then `false` is
/// returned.
///
/// See [`PageInfo` fields spec][1] for more details.
///
/// [1]: https://tinyurl.com/gql-relay#sec-undefined.PageInfo.Fields
/// [Edge]: https://tinyurl.com/gql-relay#sec-Edge-Types
pub has_next_page: bool,
/// [Cursor] pointing to the first [Node] in [Connection]'s [Edges].
///
/// [Cursor]: https://tinyurl.com/gql-relay#sec-Cursor
/// [Node]: https://tinyurl.com/gql-relay#sec-Node
/// [Connection]: https://tinyurl.com/gql-relay#sec-Connection-Types
/// [Edges]: https://tinyurl.com/gql-relay#sec-Edges
pub start_cursor: Option<&'a str>,
/// Indicator whether more [Edge]s exist prior to the set defined by the
/// clients arguments.
///
/// If the client is paginating with `last`/`before`, then `true` is
/// returned if prior [Edge]s exist, otherwise `false`.
///
/// If the client is paginating with `first`/`after`, then `false` is
/// returned.
///
/// See [`PageInfo` fields spec][1] for more details.
///
/// [1]: https://tinyurl.com/gql-relay#sec-undefined.PageInfo.Fields
/// [Edge]: https://tinyurl.com/gql-relay#sec-Edge-Types
pub has_previous_page: bool,
}
/// [`Connection`] with [`User`]s.
pub type UsersConnection = Connection<User, UsersCursor>;
/// [Connection] with `User`s.
///
/// [Connection]: https://tinyurl.com/gql-relay#sec-Connection-Types
#[graphql_object(context = Context)]
impl UsersConnection {
/// List of `User` [Edges] in this [Connection].
///
/// [Connection]: https://tinyurl.com/gql-relay#sec-Connection-Types
/// [Edges]: https://tinyurl.com/gql-relay#sel-FAFFFBEAAAACBFpwI
pub fn edges(&self) -> &[UsersEdge] {
self._edges()
}
/// List of `User`s in this [Connection].
///
/// [Connection]: https://tinyurl.com/gql-relay#sec-Connection-Types
pub fn nodes(&self) -> Vec<&User> {
self._nodes()
}
/// [PageInfo] of this [Connection].
///
/// [Connection]: https://tinyurl.com/gql-relay#sec-Connection-Types
/// [PageInfo]: https://tinyurl.com/gql-relay#sec-undefined.PageInfo
pub fn page_info(&self) -> PageInfo<'_> {
self._page_info()
}
}
/// [`Connection`]'s [`Edge`] with an [`User`].
pub type UsersEdge = Edge<User, UsersCursor>;
/// [Edge] with an `User`.
///
/// [Edge]: https://tinyurl.com/gql-relay#sec-Edge-Types
#[graphql_object(context = Context)]
impl UsersEdge {
/// `User` [Node] at the end of this [Edge].
///
/// [Edge]: https://tinyurl.com/gql-relay#sec-Edge-Types
/// [Node]: https://tinyurl.com/gql-relay#sec-Node
pub fn node(&self) -> &User {
&self.node
}
/// [Cursor] of this [Edge].
///
/// [Cursor]: https://tinyurl.com/gql-relay#sec-Cursor
/// [Edge]: https://tinyurl.com/gql-relay#sec-Edge-Types
pub fn cursor(&self) -> &UsersCursor {
&self.cursor
}
} |
Thanks for your answer! I see, so there needs to be one concrete type per "pagination" type, is that right? I also tried that, this way: #[macro_export]
macro_rules! declare_paginable {
($type: ident as $alias: ident) => {
pub struct $alias {
pub items: Vec<$type>,
pub from: i32,
pub has_more: bool,
pub total: i32,
}
#[juniper::graphql_object]
impl $alias {
pub fn items(&self) -> &[$type] {
self.items.as_slice()
}
pub fn from(&self) -> i32 {
self.from
}
pub fn has_more(&self) -> bool {
self.has_more
}
pub fn total(&self) -> i32 {
self.total
}
}
};
} But then, when I use it: #[derive(Clone)]
struct Test {
a: String,
}
crate::declare_paginable!(Test as PaginatedTest); I get the following error: Do you have an idea on how to solve this? I have a lot of type so I cannot afford to write a manual implementation by hand for each of them, which is why I'm trying to automate it. |
Yes.
Wow... never met this kind of an error 🤔 I guess because we've never used Googling on this problem says that you shoud consider |
Ok I'll look into that, thank you! |
I posted some questions on Rust's forum and for the macro part, it is possible to solve it by using a |
On the macro part, the |
Hi there!
I'm currently writing a
Paginated<T>
type for paginated responses, whereT
is a value that can be handled by GraphQL.I struggle at writing this structure, here is what I have currently:
This fails because
T
is not aGraphQLValue
. Seems logical, so I added awhere T: GraphQLValue<DefaultScalarValue>
constraint to myimpl
. But now it asks me to also implementIsOutputType
,GraphQLType
,GraphQLValueAsync
, ensure that<T as GraphQLValue>::Context: juniper::Context
, and so on.This is a lot of constraints (especially the
GraphQLValueAsync
part) so is there a simpler way to constraintT
?Thanks in advance for your help!
The text was updated successfully, but these errors were encountered: