iota_sdk_graphql_client/api/
move_objects.rs1use std::marker::PhantomData;
8
9use futures::Stream;
10use iota_move_types::MoveObject;
11use iota_types::{Address, ObjectId, ObjectReference};
12
13use crate::{
14 GraphQLClient, ListObjectsQuery,
15 api::define_query,
16 error::GraphQLResult,
17 pagination::{Page, PaginationFilter},
18 query_types::ObjectFilter,
19 streams::stream_paginated_query,
20};
21
22#[derive(Clone, Debug)]
24pub struct OwnedMoveObject<T> {
25 object_ref: ObjectReference,
26 object: T,
27}
28
29impl<T> OwnedMoveObject<T> {
30 pub fn object_ref(&self) -> ObjectReference {
32 self.object_ref
33 }
34
35 pub fn object(&self) -> &T {
37 &self.object
38 }
39
40 pub fn into_object(self) -> T {
42 self.object
43 }
44}
45
46#[derive(Clone, Debug, Default, Eq, PartialEq)]
52pub struct MoveObjectFilter {
53 owner: Option<Address>,
55 object_ids: Option<Vec<ObjectId>>,
57}
58
59impl MoveObjectFilter {
60 pub fn with_owner(mut self, owner: Address) -> Self {
62 self.owner = Some(owner);
63 self
64 }
65
66 pub fn with_object_ids(mut self, object_ids: Vec<ObjectId>) -> Self {
68 self.object_ids = Some(object_ids);
69 self
70 }
71
72 fn into_object_filter<T: MoveObject>(self) -> ObjectFilter {
74 ObjectFilter {
75 type_tag: Some(T::struct_tag().to_string()),
76 owner: self.owner,
77 object_ids: self.object_ids,
78 }
79 }
80}
81
82define_query! {
83 #[derive(Clone)]
85 pub struct ListMoveObjectsQuery<T: MoveObject> {
86 client: GraphQLClient,
87 filter: Option<MoveObjectFilter>,
88 pagination: PaginationFilter,
89 _marker: PhantomData<fn() -> T>,
90 }
91 output: GraphQLResult<Page<OwnedMoveObject<T>>>;
92}
93
94impl<T: MoveObject> ListMoveObjectsQuery<T> {
95 pub fn filter(mut self, filter: MoveObjectFilter) -> Self {
97 self.filter = Some(filter);
98 self
99 }
100
101 pub fn pagination(mut self, pagination: PaginationFilter) -> Self {
103 self.pagination = pagination;
104 self
105 }
106
107 pub fn stream(self) -> impl Stream<Item = GraphQLResult<OwnedMoveObject<T>>> + Unpin
110 where
111 T: Clone + Unpin,
112 {
113 let pagination = self.pagination.clone();
114 stream_paginated_query(move |page| self.clone().pagination(page).send(), pagination)
115 }
116
117 fn objects_query(self) -> ListObjectsQuery {
118 ListObjectsQuery::new(self.client)
119 .filter(self.filter.unwrap_or_default().into_object_filter::<T>())
120 .pagination(self.pagination)
121 }
122
123 async fn send(self) -> GraphQLResult<Page<OwnedMoveObject<T>>> {
124 let page = self.objects_query().await?;
125 let (page_info, objects) = page.into_parts();
126 let decoded = objects
127 .iter()
128 .map(|object| {
129 Ok(OwnedMoveObject {
130 object_ref: object.object_ref(),
131 object: T::try_from(object)?,
132 })
133 })
134 .collect::<GraphQLResult<Vec<_>>>()?;
135 Ok(Page::new(page_info, decoded))
136 }
137}
138
139impl GraphQLClient {
140 pub fn move_objects<T: MoveObject>(&self) -> ListMoveObjectsQuery<T> {
163 ListMoveObjectsQuery {
164 client: self.clone(),
165 filter: None,
166 pagination: PaginationFilter::default(),
167 _marker: PhantomData,
168 }
169 }
170}
171
172#[cfg(all(test, not(target_arch = "wasm32")))]
173mod tests {
174 use futures::StreamExt;
175 use iota_move_types::iota_framework::{coin::Coin, iota::IOTA};
176
177 use super::*;
178 use crate::test_utils::{
179 assert_backward_page, assert_forward_page, backward_page, forward_page, sent_variables,
180 test_client,
181 };
182
183 #[tokio::test]
184 async fn move_objects_sends_the_type_filter_and_pagination() {
185 let vars = sent_variables("ObjectsQueryFragment", |client| async move {
186 let _ = client
187 .move_objects::<Coin<IOTA>>()
188 .filter(MoveObjectFilter::default().with_owner(Address::STD))
189 .pagination(backward_page())
190 .await;
191 })
192 .await;
193 assert_eq!(vars["filter"]["owner"], Address::STD.to_string());
194 assert_eq!(
195 vars["filter"]["type"],
196 Coin::<IOTA>::struct_tag().to_string()
197 );
198 assert_backward_page(&vars);
199
200 let vars = sent_variables("ObjectsQueryFragment", |client| async move {
201 let _ = client
202 .move_objects::<Coin<IOTA>>()
203 .filter(MoveObjectFilter::default().with_owner(Address::STD))
204 .pagination(forward_page())
205 .await;
206 })
207 .await;
208 assert_forward_page(&vars);
209 }
210
211 #[tokio::test]
212 async fn test_move_objects_query() {
213 let client = test_client();
214 let coins = client
215 .move_objects::<Coin<IOTA>>()
216 .await
217 .map_err(|e| {
218 format!(
219 "Move objects query failed for {} network: Error {e}",
220 client.rpc_server()
221 )
222 })
223 .unwrap();
224 assert!(
225 !coins.is_empty(),
226 "Move objects query returned no data for {} network",
227 client.rpc_server()
228 );
229 }
230
231 #[tokio::test]
234 async fn test_move_objects_stream() {
235 let client = test_client();
236 let mut stream = client.move_objects::<Coin<IOTA>>().stream();
237 stream
238 .next()
239 .await
240 .unwrap_or_else(|| {
241 panic!(
242 "Move objects stream yielded nothing for {} network",
243 client.rpc_server()
244 )
245 })
246 .map_err(|e| {
247 format!(
248 "Move objects stream failed for {} network: Error {e}",
249 client.rpc_server()
250 )
251 })
252 .unwrap();
253 }
254}