af_oracle/graphql/
price_feeds.rs

1use af_move_type::MoveInstance;
2use af_sui_types::{ObjectId, Version};
3use enum_as_inner::EnumAsInner;
4use futures::Stream;
5use sui_gql_client::queries::fragments::{MoveValueRaw, PageInfoForward};
6use sui_gql_client::queries::{Error, GraphQlClientExt as _};
7use sui_gql_client::{GraphQlClient, GraphQlResponseExt as _, schema};
8
9type PriceFeed = MoveInstance<crate::oracle::PriceFeed>;
10
11pub(super) fn query<C: GraphQlClient>(
12    client: &C,
13    pfs: ObjectId,
14    version: Option<Version>,
15) -> impl Stream<Item = Result<(ObjectId, PriceFeed), Error<C::Error>>> + '_ {
16    async_stream::try_stream! {
17        let mut vars = Variables {
18            pfs,
19            version,
20            first: Some(client.max_page_size().await?),
21            after: None,
22        };
23        let mut has_next_page = true;
24        while has_next_page {
25            let (page_info, price_feeds) = request(client, vars.clone()).await?;
26
27            vars.after = page_info.end_cursor.clone();
28            has_next_page = page_info.has_next_page;
29
30            for value in price_feeds {
31                yield value;
32            }
33        }
34    }
35}
36
37async fn request<C: GraphQlClient>(
38    client: &C,
39    vars: Variables,
40) -> Result<
41    (
42        PageInfoForward,
43        impl Iterator<Item = (ObjectId, PriceFeed)> + 'static,
44    ),
45    Error<C::Error>,
46> {
47    let response = client
48        .query::<Query, _>(vars)
49        .await
50        .map_err(Error::Client)?;
51    let data = response.try_into_data()?;
52
53    let PfsDfsConnection { nodes, page_info } = extract(data)?;
54    Ok((page_info, nodes.into_iter().filter_map(filter_df)))
55}
56
57fn extract(data: Option<Query>) -> Result<PfsDfsConnection, &'static str> {
58    graphql_extract::extract!(data => {
59        price_feed_storage? {
60            dfs
61        }
62    });
63    Ok(dfs)
64}
65
66fn filter_df(df: PfsDf) -> Option<(ObjectId, PriceFeed)> {
67    let df_name: MoveInstance<crate::keys::PriceFeedForSource> = df.df_name?.try_into().ok()?;
68    let df_value_raw = df.df_value?.into_move_value().ok();
69    let df_value: PriceFeed = df_value_raw?.try_into().ok()?;
70
71    Some((df_name.value.source_wrapper_id.bytes, df_value))
72}
73
74#[cfg(test)]
75#[allow(clippy::unwrap_used)]
76#[test]
77fn gql_output() {
78    use cynic::QueryBuilder as _;
79
80    let vars = Variables {
81        pfs: ObjectId::ZERO,
82        version: None,
83        first: Some(10),
84        after: None,
85    };
86    let operation = Query::build(vars);
87    insta::assert_snapshot!(operation.query, @r###"
88    query Query($pfs: SuiAddress!, $version: UInt53, $first: Int, $after: String) {
89      price_feed_storage: object(address: $pfs, version: $version) {
90        dfs: dynamicFields(first: $first, after: $after) {
91          nodes {
92            df_name: name {
93              type {
94                repr
95              }
96              bcs
97            }
98            df_value: value {
99              __typename
100              ... on MoveValue {
101                type {
102                  repr
103                }
104                bcs
105              }
106            }
107          }
108          pageInfo {
109            hasNextPage
110            endCursor
111          }
112        }
113      }
114    }
115    "###);
116}
117
118#[derive(cynic::QueryVariables, Clone, Debug)]
119struct Variables {
120    pfs: ObjectId,
121    version: Option<Version>,
122    first: Option<i32>,
123    after: Option<String>,
124}
125
126#[derive(cynic::QueryFragment, Debug)]
127#[cynic(variables = "Variables")]
128struct Query {
129    #[arguments(address: $pfs, version: $version)]
130    #[cynic(alias, rename = "object")]
131    price_feed_storage: Option<PfsObject>,
132}
133
134#[derive(cynic::QueryFragment, Debug)]
135#[cynic(graphql_type = "Object", variables = "Variables")]
136struct PfsObject {
137    #[arguments(first: $first, after: $after)]
138    #[cynic(alias, rename = "dynamicFields")]
139    dfs: PfsDfsConnection,
140}
141
142#[derive(cynic::QueryFragment, Debug)]
143#[cynic(graphql_type = "DynamicFieldConnection")]
144struct PfsDfsConnection {
145    nodes: Vec<PfsDf>,
146    page_info: PageInfoForward,
147}
148
149#[derive(cynic::QueryFragment, Debug)]
150#[cynic(graphql_type = "DynamicField")]
151struct PfsDf {
152    #[cynic(alias, rename = "name")]
153    df_name: Option<MoveValueRaw>,
154    #[cynic(alias, rename = "value")]
155    df_value: Option<PfsDfValue>,
156}
157
158#[derive(cynic::InlineFragments, Debug, EnumAsInner)]
159#[cynic(graphql_type = "DynamicFieldValue")]
160enum PfsDfValue {
161    MoveValue(MoveValueRaw),
162    #[cynic(fallback)]
163    Unknown,
164}