async_graphql/types/connection/
mod.rs

1//! Types for Relay-compliant server
2
3mod connection_type;
4mod cursor;
5mod edge;
6mod page_info;
7
8use std::{fmt::Display, future::Future};
9
10pub use connection_type::Connection;
11pub use cursor::{CursorType, OpaqueCursor};
12pub use edge::Edge;
13pub use page_info::PageInfo;
14
15use crate::{Error, ObjectType, OutputType, Result, SimpleObject};
16
17/// Empty additional fields
18#[derive(SimpleObject)]
19#[graphql(internal, fake)]
20pub struct EmptyFields;
21
22/// Used to specify the edge name.
23pub trait EdgeNameType: Send + Sync {
24    /// Returns the edge type name.
25    fn type_name<T: OutputType>() -> String;
26}
27
28/// Name the edge type by default with the default format.
29pub struct DefaultEdgeName;
30
31impl EdgeNameType for DefaultEdgeName {
32    fn type_name<T: OutputType>() -> String {
33        format!("{}Edge", T::type_name())
34    }
35}
36
37/// Used to specify the connection name.
38pub trait ConnectionNameType: Send + Sync {
39    /// Returns the connection type name.
40    fn type_name<T: OutputType>() -> String;
41}
42
43/// Name the connection type by default with the default format.
44pub struct DefaultConnectionName;
45
46impl ConnectionNameType for DefaultConnectionName {
47    fn type_name<T: OutputType>() -> String {
48        format!("{}Connection", T::type_name())
49    }
50}
51
52mod private {
53    pub trait NodesFieldSwitcher: Send + Sync {}
54
55    impl NodesFieldSwitcher for super::DisableNodesField {}
56    impl NodesFieldSwitcher for super::EnableNodesField {}
57}
58
59/// Allow switch if [`Connection`] contains `nodes` field in GQL output
60///
61/// This trait is sealed and can not be implemented outside of this crate.
62pub trait NodesFieldSwitcherSealed: private::NodesFieldSwitcher {}
63
64impl NodesFieldSwitcherSealed for DisableNodesField {}
65impl NodesFieldSwitcherSealed for EnableNodesField {}
66
67/// Enable (at compile time) `nodes` field in GQL output of [`Connection`]
68pub struct EnableNodesField;
69
70/// Disable (at compile time) `nodes` field in GQL output of [`Connection`]
71pub struct DisableNodesField;
72
73/// Parses the parameters and executes the query.
74///
75/// # Examples
76///
77/// ```rust
78/// use std::borrow::Cow;
79///
80/// use async_graphql::*;
81/// use async_graphql::types::connection::*;
82///
83/// struct Query;
84///
85/// struct Numbers;
86///
87/// #[derive(SimpleObject)]
88/// struct Diff {
89///     diff: i32,
90/// }
91///
92/// #[Object]
93/// impl Query {
94///     async fn numbers(&self,
95///         after: Option<String>,
96///         before: Option<String>,
97///         first: Option<i32>,
98///         last: Option<i32>
99///     ) -> Result<Connection<usize, i32, EmptyFields, Diff>> {
100///         query(after, before, first, last, |after, before, first, last| async move {
101///             let mut start = after.map(|after| after + 1).unwrap_or(0);
102///             let mut end = before.unwrap_or(10000);
103///             if let Some(first) = first {
104///                 end = (start + first).min(end);
105///             }
106///             if let Some(last) = last {
107///                 start = if last > end - start {
108///                     end
109///                 } else {
110///                     end - last
111///                 };
112///             }
113///             let mut connection = Connection::new(start > 0, end < 10000);
114///             connection.edges.extend(
115///                 (start..end).into_iter().map(|n|
116///                     Edge::with_additional_fields(n, n as i32, Diff{ diff: (10000 - n) as i32 })),
117///             );
118///             Ok::<_, Error>(connection)
119///         }).await
120///     }
121/// }
122///
123/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
124/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
125///
126/// assert_eq!(schema.execute("{ numbers(first: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({
127///     "numbers": {
128///         "edges": [
129///             {"node": 0, "diff": 10000},
130///             {"node": 1, "diff": 9999},
131///         ]
132///     },
133/// }));
134///
135/// assert_eq!(schema.execute("{ numbers(last: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({
136///     "numbers": {
137///         "edges": [
138///             {"node": 9998, "diff": 2},
139///             {"node": 9999, "diff": 1},
140///         ]
141///     },
142/// }));
143/// # });
144/// ```
145///
146/// # Custom connection and edge type names
147///
148/// ```
149/// use async_graphql::{connection::*, *};
150///
151/// #[derive(SimpleObject)]
152/// struct MyObj {
153///     a: i32,
154///     b: String,
155/// }
156///
157/// // Use to custom connection name
158/// struct MyConnectionName;
159///
160/// impl ConnectionNameType for MyConnectionName {
161///     fn type_name<T: OutputType>() -> String {
162///         "MyConnection".to_string()
163///     }
164/// }
165///
166/// // Use to custom edge name
167/// struct MyEdgeName;
168///
169/// impl EdgeNameType for MyEdgeName {
170///     fn type_name<T: OutputType>() -> String {
171///         "MyEdge".to_string()
172///     }
173/// }
174///
175/// struct Query;
176///
177/// #[Object]
178/// impl Query {
179///     async fn numbers(
180///         &self,
181///         after: Option<String>,
182///         before: Option<String>,
183///         first: Option<i32>,
184///         last: Option<i32>,
185///     ) -> Connection<usize, MyObj, EmptyFields, EmptyFields, MyConnectionName, MyEdgeName> {
186///         let mut connection = Connection::new(false, false);
187///         connection.edges.push(Edge::new(1, MyObj { a: 100, b: "abc".to_string() }));
188///         connection
189///     }
190/// }
191///
192/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
193/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
194///
195/// let query = r#"{
196///     numbers(first: 2) {
197///         __typename
198///         edges { __typename node { a b } }
199///     }
200/// }"#;
201/// let data = schema.execute(query).await.into_result().unwrap().data;
202/// assert_eq!(data, value!({
203///     "numbers": {
204///         "__typename": "MyConnection",
205///         "edges": [
206///             {"__typename": "MyEdge", "node": { "a": 100, "b": "abc" }},
207///         ]
208///     },
209/// }));
210/// # });
211/// ```
212pub async fn query<
213    Name,
214    EdgeName,
215    Cursor,
216    Node,
217    NodesVersion,
218    ConnectionFields,
219    EdgeFields,
220    F,
221    R,
222    E,
223>(
224    after: Option<String>,
225    before: Option<String>,
226    first: Option<i32>,
227    last: Option<i32>,
228    f: F,
229) -> Result<Connection<Cursor, Node, ConnectionFields, EdgeFields, Name, EdgeName, NodesVersion>>
230where
231    Name: ConnectionNameType,
232    EdgeName: EdgeNameType,
233    Cursor: CursorType + Send + Sync,
234    <Cursor as CursorType>::Error: Display + Send + Sync + 'static,
235    Node: OutputType,
236    NodesVersion: NodesFieldSwitcherSealed,
237    ConnectionFields: ObjectType,
238    EdgeFields: ObjectType,
239    F: FnOnce(Option<Cursor>, Option<Cursor>, Option<usize>, Option<usize>) -> R,
240    R: Future<
241        Output = Result<
242            Connection<Cursor, Node, ConnectionFields, EdgeFields, Name, EdgeName, NodesVersion>,
243            E,
244        >,
245    >,
246    E: Into<Error>,
247{
248    query_with(after, before, first, last, f).await
249}
250
251/// Parses the parameters and executes the query and return a custom
252/// `Connection` type.
253///
254/// `Connection<T>` and `Edge<T>` have certain limitations. For example, you
255/// cannot customize the name of the type, so you can use this function to
256/// execute the query and return a customized `Connection` type.
257///
258/// # Examples
259///
260/// ```rust
261/// 
262/// use async_graphql::*;
263/// use async_graphql::types::connection::*;
264///
265/// #[derive(SimpleObject)]
266/// struct MyEdge {
267///     cursor: usize,
268///     node: i32,
269///     diff: i32,
270/// }
271///
272/// #[derive(SimpleObject)]
273/// struct MyConnection {
274///     edges: Vec<MyEdge>,
275///     page_info: PageInfo,
276/// }
277///
278/// struct Query;
279///
280/// #[Object]
281/// impl Query {
282///     async fn numbers(&self,
283///         after: Option<String>,
284///         before: Option<String>,
285///         first: Option<i32>,
286///         last: Option<i32>
287///     ) -> Result<MyConnection> {
288///         query_with(after, before, first, last, |after, before, first, last| async move {
289///             let mut start = after.map(|after| after + 1).unwrap_or(0);
290///             let mut end = before.unwrap_or(10000);
291///             if let Some(first) = first {
292///                 end = (start + first).min(end);
293///             }
294///             if let Some(last) = last {
295///                 start = if last > end - start {
296///                     end
297///                 } else {
298///                     end - last
299///                 };
300///             }
301///             let connection = MyConnection {
302///                 edges: (start..end).into_iter().map(|n| MyEdge {
303///                     cursor: n,
304///                     node: n as i32,
305///                     diff: (10000 - n) as i32,
306///                 }).collect(),
307///                 page_info: PageInfo {
308///                     has_previous_page: start > 0,
309///                     has_next_page: end < 10000,
310///                     start_cursor: Some(start.encode_cursor()),
311///                     end_cursor: Some(end.encode_cursor()),
312///                 },
313///             };
314///             Ok::<_, Error>(connection)
315///         }).await
316///     }
317/// }
318///
319/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
320/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
321///
322/// assert_eq!(schema.execute("{ numbers(first: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({
323///     "numbers": {
324///         "edges": [
325///             {"node": 0, "diff": 10000},
326///             {"node": 1, "diff": 9999},
327///         ]
328///     },
329/// }));
330///
331/// assert_eq!(schema.execute("{ numbers(last: 2) { edges { node diff } } }").await.into_result().unwrap().data, value!({
332///     "numbers": {
333///         "edges": [
334///             {"node": 9998, "diff": 2},
335///             {"node": 9999, "diff": 1},
336///         ]
337///     },
338/// }));
339/// # });
340/// ```
341pub async fn query_with<Cursor, T, F, R, E>(
342    after: Option<String>,
343    before: Option<String>,
344    first: Option<i32>,
345    last: Option<i32>,
346    f: F,
347) -> Result<T>
348where
349    Cursor: CursorType + Send + Sync,
350    <Cursor as CursorType>::Error: Display + Send + Sync + 'static,
351    F: FnOnce(Option<Cursor>, Option<Cursor>, Option<usize>, Option<usize>) -> R,
352    R: Future<Output = Result<T, E>>,
353    E: Into<Error>,
354{
355    let first = match first {
356        Some(first) if first < 0 => {
357            return Err(Error::new(
358                "The \"first\" parameter must be a non-negative number",
359            ));
360        }
361        Some(first) => Some(first as usize),
362        None => None,
363    };
364
365    let last = match last {
366        Some(last) if last < 0 => {
367            return Err(Error::new(
368                "The \"last\" parameter must be a non-negative number",
369            ));
370        }
371        Some(last) => Some(last as usize),
372        None => None,
373    };
374
375    let before = match before {
376        Some(before) => Some(Cursor::decode_cursor(&before).map_err(Error::new_with_source)?),
377        None => None,
378    };
379
380    let after = match after {
381        Some(after) => Some(Cursor::decode_cursor(&after).map_err(Error::new_with_source)?),
382        None => None,
383    };
384
385    f(after, before, first, last).await.map_err(Into::into)
386}