iota_graphql_rpc/
connection.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{borrow::Cow, marker::PhantomData};
6
7use async_graphql::{
8    Object, ObjectType, OutputType, TypeName,
9    connection::{
10        ConnectionNameType, CursorType, DefaultConnectionName, DefaultEdgeName, Edge, EdgeNameType,
11        EmptyFields, EnableNodesField, NodesFieldSwitcherSealed, PageInfo,
12    },
13};
14
15/// Mirrors the `Connection` type from async-graphql, with the exception that if
16/// `start_cursor` and/ or `end_cursor` is set on the struct, then when
17/// `page_info` is called, it will use those values before deferring to `edges`.
18/// The default implementation derives these cursors from the first and
19/// last element of `edges`, so if `edges` is empty, both are set to null. This
20/// is undesirable for queries that make use of `scan_limit`; when the scan
21/// limit is reached, a caller can continue to paginate forwards or backwards
22/// until all candidates in the scanning range have been visited, even if the
23/// current page yields no results.
24pub(crate) struct ScanConnection<
25    Cursor,
26    Node,
27    EdgeFields = EmptyFields,
28    Name = DefaultConnectionName,
29    EdgeName = DefaultEdgeName,
30    NodesField = EnableNodesField,
31> where
32    Cursor: CursorType + Send + Sync,
33    Node: OutputType,
34    EdgeFields: ObjectType,
35    Name: ConnectionNameType,
36    EdgeName: EdgeNameType,
37    NodesField: NodesFieldSwitcherSealed,
38{
39    _mark1: PhantomData<Name>,
40    _mark2: PhantomData<EdgeName>,
41    _mark3: PhantomData<NodesField>,
42    pub edges: Vec<Edge<Cursor, Node, EdgeFields, EdgeName>>,
43    pub has_previous_page: bool,
44    pub has_next_page: bool,
45    pub start_cursor: Option<String>,
46    pub end_cursor: Option<String>,
47}
48
49#[Object(name_type)]
50impl<Cursor, Node, EdgeFields, Name, EdgeName>
51    ScanConnection<Cursor, Node, EdgeFields, Name, EdgeName, EnableNodesField>
52where
53    Cursor: CursorType + Send + Sync,
54    Node: OutputType,
55    EdgeFields: ObjectType,
56    Name: ConnectionNameType,
57    EdgeName: EdgeNameType,
58{
59    /// Information to aid in pagination.
60    async fn page_info(&self) -> PageInfo {
61        // Unlike the default implementation, this Connection will use `start_cursor`
62        // and `end_cursor` if they are `Some`.
63        PageInfo {
64            has_previous_page: self.has_previous_page,
65            has_next_page: self.has_next_page,
66            start_cursor: self
67                .start_cursor
68                .clone()
69                .or_else(|| self.edges.first().map(|edge| edge.cursor.encode_cursor())),
70            end_cursor: self
71                .end_cursor
72                .clone()
73                .or_else(|| self.edges.last().map(|edge| edge.cursor.encode_cursor())),
74        }
75    }
76
77    /// A list of edges.
78    #[inline]
79    async fn edges(&self) -> &[Edge<Cursor, Node, EdgeFields, EdgeName>] {
80        &self.edges
81    }
82
83    /// A list of nodes.
84    async fn nodes(&self) -> Vec<&Node> {
85        self.edges.iter().map(|e| &e.node).collect()
86    }
87}
88
89impl<Cursor, Node, NodesField, EdgeFields, Name, EdgeName>
90    ScanConnection<Cursor, Node, EdgeFields, Name, EdgeName, NodesField>
91where
92    Cursor: CursorType + Send + Sync,
93    Node: OutputType,
94    EdgeFields: ObjectType,
95    Name: ConnectionNameType,
96    EdgeName: EdgeNameType,
97    NodesField: NodesFieldSwitcherSealed,
98{
99    /// Create a new connection.
100    #[inline]
101    pub fn new(has_previous_page: bool, has_next_page: bool) -> Self {
102        ScanConnection {
103            _mark1: PhantomData,
104            _mark2: PhantomData,
105            _mark3: PhantomData,
106            edges: Vec::new(),
107            has_previous_page,
108            has_next_page,
109            start_cursor: None,
110            end_cursor: None,
111        }
112    }
113}
114
115impl<Cursor, Node, EdgeFields, Name, EdgeName, NodesField> TypeName
116    for ScanConnection<Cursor, Node, EdgeFields, Name, EdgeName, NodesField>
117where
118    Cursor: CursorType + Send + Sync,
119    Node: OutputType,
120    EdgeFields: ObjectType,
121    Name: ConnectionNameType,
122    EdgeName: EdgeNameType,
123    NodesField: NodesFieldSwitcherSealed,
124{
125    #[inline]
126    fn type_name() -> Cow<'static, str> {
127        Name::type_name::<Node>().into()
128    }
129}