1use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 client::{Error, Result},
12 types::block::address::Bech32Address,
13};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct QueryParameters(Vec<QueryParameter>);
20
21impl QueryParameters {
22 #[must_use]
24 pub fn new(query_parameters: impl Into<Vec<QueryParameter>>) -> Self {
25 let mut query_parameters = query_parameters.into();
26 query_parameters.sort_unstable_by_key(QueryParameter::kind);
27 query_parameters.dedup_by_key(|qp| qp.kind());
28
29 Self(query_parameters)
30 }
31
32 pub fn empty() -> Self {
34 Self(Vec::new())
35 }
36
37 pub fn replace(&mut self, query_parameter: QueryParameter) {
39 match self
40 .0
41 .binary_search_by_key(&query_parameter.kind(), QueryParameter::kind)
42 {
43 Ok(pos) => self.0[pos] = query_parameter,
44 Err(pos) => self.0.insert(pos, query_parameter),
45 }
46 }
47
48 pub(crate) fn contains(&self, kind: u8) -> bool {
50 self.0.iter().any(|q| q.kind() == kind)
51 }
52
53 #[cfg(test)]
55 pub(crate) fn any<F: Fn(&QueryParameter) -> bool>(&self, f: F) -> bool {
56 self.0.iter().any(f)
57 }
58
59 pub fn to_query_string(&self) -> Option<String> {
61 if self.0.is_empty() {
62 None
63 } else {
64 Some(
65 self.0
66 .iter()
67 .map(QueryParameter::to_query_string)
68 .collect::<Vec<String>>()
69 .join("&"),
70 )
71 }
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
77#[serde(rename_all = "camelCase")]
78#[non_exhaustive]
79pub enum QueryParameter {
80 Address(Bech32Address),
82 AliasAddress(Bech32Address),
84 CreatedAfter(u32),
86 CreatedBefore(u32),
88 Cursor(String),
90 ExpirationReturnAddress(Bech32Address),
93 ExpiresAfter(u32),
95 ExpiresBefore(u32),
97 Governor(Bech32Address),
99 HasExpiration(bool),
101 HasNativeTokens(bool),
103 HasStorageDepositReturn(bool),
105 HasTimelock(bool),
107 Issuer(Bech32Address),
109 MaxNativeTokenCount(u32),
111 MinNativeTokenCount(u32),
113 PageSize(usize),
116 Sender(Bech32Address),
118 StateController(Bech32Address),
120 StorageDepositReturnAddress(Bech32Address),
123 Tag(String),
125 TimelockedAfter(u32),
127 TimelockedBefore(u32),
129 UnlockableByAddress(Bech32Address),
131}
132
133impl QueryParameter {
134 fn to_query_string(&self) -> String {
135 match self {
136 Self::Address(v) => format!("address={v}"),
137 Self::AliasAddress(v) => format!("aliasAddress={v}"),
138 Self::CreatedAfter(v) => format!("createdAfter={v}"),
139 Self::CreatedBefore(v) => format!("createdBefore={v}"),
140 Self::Cursor(v) => format!("cursor={v}"),
141 Self::ExpirationReturnAddress(v) => format!("expirationReturnAddress={v}"),
142 Self::ExpiresAfter(v) => format!("expiresAfter={v}"),
143 Self::ExpiresBefore(v) => format!("expiresBefore={v}"),
144 Self::Governor(v) => format!("governor={v}"),
145 Self::HasExpiration(v) => format!("hasExpiration={v}"),
146 Self::HasNativeTokens(v) => format!("hasNativeTokens={v}"),
147 Self::HasStorageDepositReturn(v) => format!("hasStorageDepositReturn={v}"),
148 Self::HasTimelock(v) => format!("hasTimelock={v}"),
149 Self::Issuer(v) => format!("issuer={v}"),
150 Self::MaxNativeTokenCount(v) => format!("maxNativeTokenCount={v}"),
151 Self::MinNativeTokenCount(v) => format!("minNativeTokenCount={v}"),
152 Self::PageSize(v) => format!("pageSize={v}"),
153 Self::Sender(v) => format!("sender={v}"),
154 Self::StateController(v) => format!("stateController={v}"),
155 Self::StorageDepositReturnAddress(v) => format!("storageDepositReturnAddress={v}"),
156 Self::Tag(v) => format!("tag={v}"),
157 Self::TimelockedAfter(v) => format!("timelockedAfter={v}"),
158 Self::TimelockedBefore(v) => format!("timelockedBefore={v}"),
159 Self::UnlockableByAddress(v) => format!("unlockableByAddress={v}"),
160 }
161 }
162
163 pub(crate) fn kind(&self) -> u8 {
164 match self {
165 Self::Address(_) => 0,
166 Self::AliasAddress(_) => 1,
167 Self::CreatedAfter(_) => 2,
168 Self::CreatedBefore(_) => 3,
169 Self::Cursor(_) => 4,
170 Self::ExpirationReturnAddress(_) => 5,
171 Self::ExpiresAfter(_) => 6,
172 Self::ExpiresBefore(_) => 7,
173 Self::Governor(_) => 8,
174 Self::HasExpiration(_) => 9,
175 Self::HasNativeTokens(_) => 10,
176 Self::HasStorageDepositReturn(_) => 11,
177 Self::HasTimelock(_) => 12,
178 Self::Issuer(_) => 13,
179 Self::MaxNativeTokenCount(_) => 14,
180 Self::MinNativeTokenCount(_) => 15,
181 Self::PageSize(_) => 16,
182 Self::Sender(_) => 17,
183 Self::StateController(_) => 18,
184 Self::StorageDepositReturnAddress(_) => 19,
185 Self::Tag(_) => 20,
186 Self::TimelockedAfter(_) => 21,
187 Self::TimelockedBefore(_) => 22,
188 Self::UnlockableByAddress(_) => 23,
189 }
190 }
191}
192
193impl fmt::Display for QueryParameter {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 write!(f, "{}", self.to_query_string())
196 }
197}
198
199macro_rules! verify_query_parameters {
200 ($query_parameters:ident, $first:path $(, $rest:path)*) => {
201 if let Some(qp) = $query_parameters.iter().find(|qp| {
202 !matches!(qp, $first(_) $(| $rest(_))*)
203 }) {
204 Err(Error::UnsupportedQueryParameter(qp.clone()))
205 } else {
206 Ok(())
207 }
208 };
209}
210
211pub(crate) fn verify_query_parameters_outputs(query_parameters: Vec<QueryParameter>) -> Result<QueryParameters> {
212 verify_query_parameters!(
213 query_parameters,
214 QueryParameter::HasNativeTokens,
215 QueryParameter::MinNativeTokenCount,
216 QueryParameter::MaxNativeTokenCount,
217 QueryParameter::CreatedBefore,
218 QueryParameter::CreatedAfter,
219 QueryParameter::PageSize,
220 QueryParameter::Cursor,
221 QueryParameter::UnlockableByAddress
222 )?;
223
224 Ok(QueryParameters::new(query_parameters))
225}
226
227pub(crate) fn verify_query_parameters_basic_outputs(query_parameters: Vec<QueryParameter>) -> Result<QueryParameters> {
228 verify_query_parameters!(
229 query_parameters,
230 QueryParameter::Address,
231 QueryParameter::HasNativeTokens,
232 QueryParameter::MinNativeTokenCount,
233 QueryParameter::MaxNativeTokenCount,
234 QueryParameter::HasStorageDepositReturn,
235 QueryParameter::StorageDepositReturnAddress,
236 QueryParameter::HasTimelock,
237 QueryParameter::TimelockedBefore,
238 QueryParameter::TimelockedAfter,
239 QueryParameter::HasExpiration,
240 QueryParameter::ExpiresBefore,
241 QueryParameter::ExpiresAfter,
242 QueryParameter::ExpirationReturnAddress,
243 QueryParameter::Sender,
244 QueryParameter::Tag,
245 QueryParameter::CreatedBefore,
246 QueryParameter::CreatedAfter,
247 QueryParameter::PageSize,
248 QueryParameter::Cursor,
249 QueryParameter::UnlockableByAddress
250 )?;
251
252 Ok(QueryParameters::new(query_parameters))
253}
254
255pub(crate) fn verify_query_parameters_alias_outputs(query_parameters: Vec<QueryParameter>) -> Result<QueryParameters> {
256 verify_query_parameters!(
257 query_parameters,
258 QueryParameter::StateController,
259 QueryParameter::Governor,
260 QueryParameter::Issuer,
261 QueryParameter::Sender,
262 QueryParameter::HasNativeTokens,
263 QueryParameter::MinNativeTokenCount,
264 QueryParameter::MaxNativeTokenCount,
265 QueryParameter::CreatedBefore,
266 QueryParameter::CreatedAfter,
267 QueryParameter::PageSize,
268 QueryParameter::Cursor,
269 QueryParameter::UnlockableByAddress
270 )?;
271
272 Ok(QueryParameters::new(query_parameters))
273}
274
275pub(crate) fn verify_query_parameters_foundry_outputs(
276 query_parameters: Vec<QueryParameter>,
277) -> Result<QueryParameters> {
278 verify_query_parameters!(
279 query_parameters,
280 QueryParameter::AliasAddress,
281 QueryParameter::HasNativeTokens,
282 QueryParameter::MinNativeTokenCount,
283 QueryParameter::MaxNativeTokenCount,
284 QueryParameter::CreatedBefore,
285 QueryParameter::CreatedAfter,
286 QueryParameter::PageSize,
287 QueryParameter::Cursor
288 )?;
289
290 Ok(QueryParameters::new(query_parameters))
291}
292
293pub(crate) fn verify_query_parameters_nft_outputs(query_parameters: Vec<QueryParameter>) -> Result<QueryParameters> {
294 verify_query_parameters!(
295 query_parameters,
296 QueryParameter::Address,
297 QueryParameter::HasNativeTokens,
298 QueryParameter::MinNativeTokenCount,
299 QueryParameter::MaxNativeTokenCount,
300 QueryParameter::HasStorageDepositReturn,
301 QueryParameter::StorageDepositReturnAddress,
302 QueryParameter::HasTimelock,
303 QueryParameter::TimelockedBefore,
304 QueryParameter::TimelockedAfter,
305 QueryParameter::HasExpiration,
306 QueryParameter::ExpiresBefore,
307 QueryParameter::ExpiresAfter,
308 QueryParameter::ExpirationReturnAddress,
309 QueryParameter::Issuer,
310 QueryParameter::Sender,
311 QueryParameter::Tag,
312 QueryParameter::CreatedBefore,
313 QueryParameter::CreatedAfter,
314 QueryParameter::PageSize,
315 QueryParameter::Cursor,
316 QueryParameter::UnlockableByAddress
317 )?;
318
319 Ok(QueryParameters::new(query_parameters))
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn query_parameter() {
328 let address1 = QueryParameter::Address(
329 Bech32Address::try_from_str("atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r").unwrap(),
330 );
331 let address2 = QueryParameter::Address(
332 Bech32Address::try_from_str("atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r").unwrap(),
333 );
334 let address3 = QueryParameter::Address(
335 Bech32Address::try_from_str("atoi1qprxpfvaz2peggq6f8k9cj8zfsxuw69e4nszjyv5kuf8yt70t2847shpjak").unwrap(),
336 );
337 let state_controller = QueryParameter::StateController(
338 Bech32Address::try_from_str("atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r").unwrap(),
339 );
340
341 let mut query_parameters = QueryParameters::new([address1, address2, state_controller]);
342 assert!(query_parameters.0.len() == 2);
344 query_parameters.replace(address3);
346 assert!(query_parameters.0.len() == 2);
347 assert!(query_parameters.any(|param| matches!(param, QueryParameter::Address(_))));
349 assert!(!query_parameters.any(|param| matches!(param, QueryParameter::Cursor(_))));
351 }
352}