crypto_pay_api/models/transfer/
builder.rs1use std::marker::PhantomData;
2
3use rust_decimal::Decimal;
4
5use crate::{
6 api::ExchangeRateAPI,
7 client::CryptoBot,
8 error::{CryptoBotError, CryptoBotResult, ValidationErrorKind},
9 models::{CryptoCurrencyCode, Missing, Set},
10 validation::{validate_amount, validate_count, ContextValidate, FieldValidate, ValidationContext},
11};
12
13use super::params::{GetTransfersParams, TransferParams};
14
15#[derive(Debug, Default)]
18pub struct GetTransfersParamsBuilder {
19 asset: Option<CryptoCurrencyCode>,
20 transfer_ids: Option<Vec<u64>>,
21 spend_id: Option<String>,
22 offset: Option<u32>,
23 count: Option<u16>,
24}
25
26impl GetTransfersParamsBuilder {
27 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn asset(mut self, asset: CryptoCurrencyCode) -> Self {
35 self.asset = Some(asset);
36 self
37 }
38
39 pub fn transfer_ids(mut self, ids: Vec<u64>) -> Self {
42 self.transfer_ids = Some(ids);
43 self
44 }
45
46 pub fn spend_id(mut self, spend_id: impl Into<String>) -> Self {
49 self.spend_id = Some(spend_id.into());
50 self
51 }
52
53 pub fn offset(mut self, offset: u32) -> Self {
57 self.offset = Some(offset);
58 self
59 }
60
61 pub fn count(mut self, count: u16) -> Self {
64 self.count = Some(count);
65 self
66 }
67}
68
69impl FieldValidate for GetTransfersParamsBuilder {
70 fn validate(&self) -> CryptoBotResult<()> {
71 if let Some(count) = &self.count {
72 validate_count(*count)?;
73 }
74 Ok(())
75 }
76}
77
78impl GetTransfersParamsBuilder {
79 pub fn build(self) -> CryptoBotResult<GetTransfersParams> {
80 self.validate()?;
81
82 Ok(GetTransfersParams::new(
83 self.asset,
84 self.transfer_ids,
85 self.spend_id,
86 self.offset,
87 self.count,
88 ))
89 }
90}
91
92#[derive(Debug)]
97pub struct TransferParamsBuilder<U = Missing, A = Missing, M = Missing, S = Missing> {
98 user_id: u64,
99 asset: CryptoCurrencyCode,
100 amount: Decimal,
101 spend_id: String,
102 comment: Option<String>,
103 disable_send_notification: Option<bool>,
104 _state: PhantomData<(U, A, M, S)>,
105}
106
107impl TransferParamsBuilder<Missing, Missing, Missing, Missing> {
108 pub fn new() -> TransferParamsBuilder<Missing, Missing, Missing, Missing> {
110 Self {
111 user_id: 0,
112 asset: CryptoCurrencyCode::Ton,
113 amount: Decimal::ZERO,
114 spend_id: String::new(),
115 comment: None,
116 disable_send_notification: None,
117 _state: PhantomData,
118 }
119 }
120}
121
122impl Default for TransferParamsBuilder<Missing, Missing, Missing, Missing> {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128impl<A, M, S> TransferParamsBuilder<Missing, A, M, S> {
129 pub fn user_id(mut self, user_id: u64) -> TransferParamsBuilder<Set, A, M, S> {
131 self.user_id = user_id;
132 self.transform()
133 }
134}
135
136impl<U, M, S> TransferParamsBuilder<U, Missing, M, S> {
137 pub fn asset(mut self, asset: CryptoCurrencyCode) -> TransferParamsBuilder<U, Set, M, S> {
139 self.asset = asset;
140 self.transform()
141 }
142}
143
144impl<U, A, S> TransferParamsBuilder<U, A, Missing, S> {
145 pub fn amount(mut self, amount: Decimal) -> TransferParamsBuilder<U, A, Set, S> {
148 self.amount = amount;
149 self.transform()
150 }
151}
152
153impl<U, A, M> TransferParamsBuilder<U, A, M, Missing> {
154 pub fn spend_id(mut self, spend_id: impl Into<String>) -> TransferParamsBuilder<U, A, M, Set> {
159 self.spend_id = spend_id.into();
160 self.transform()
161 }
162}
163
164impl<U, A, M, S> TransferParamsBuilder<U, A, M, S> {
165 pub fn comment(mut self, comment: impl Into<String>) -> Self {
170 self.comment = Some(comment.into());
171 self
172 }
173
174 pub fn disable_send_notification(mut self, disable: bool) -> Self {
178 self.disable_send_notification = Some(disable);
179 self
180 }
181
182 fn transform<U2, A2, M2, S2>(self) -> TransferParamsBuilder<U2, A2, M2, S2> {
183 TransferParamsBuilder {
184 user_id: self.user_id,
185 asset: self.asset,
186 amount: self.amount,
187 spend_id: self.spend_id,
188 comment: self.comment,
189 disable_send_notification: self.disable_send_notification,
190 _state: PhantomData,
191 }
192 }
193}
194
195impl FieldValidate for TransferParamsBuilder<Set, Set, Set, Set> {
196 fn validate(&self) -> CryptoBotResult<()> {
197 if self.spend_id.chars().count() > 64 {
198 return Err(CryptoBotError::ValidationError {
199 kind: ValidationErrorKind::Range,
200 message: "Spend ID must be less than 64 symbols".to_string(),
201 field: Some("spend_id".to_string()),
202 });
203 }
204
205 if let Some(comment) = &self.comment {
206 if comment.chars().count() > 1024 {
207 return Err(CryptoBotError::ValidationError {
208 kind: ValidationErrorKind::Range,
209 message: "Comment must be less than 1024 symbols".to_string(),
210 field: Some("comment".to_string()),
211 });
212 }
213 }
214
215 Ok(())
216 }
217}
218
219#[async_trait::async_trait]
220impl ContextValidate for TransferParamsBuilder<Set, Set, Set, Set> {
221 async fn validate_with_context(&self, ctx: &ValidationContext) -> CryptoBotResult<()> {
222 validate_amount(&self.amount, &self.asset, ctx).await
223 }
224}
225
226impl TransferParamsBuilder<Set, Set, Set, Set> {
227 pub async fn build(self, client: &CryptoBot) -> CryptoBotResult<TransferParams> {
228 self.validate()?;
229
230 let rates = client.get_exchange_rates().await?;
231
232 let ctx = ValidationContext { exchange_rates: rates };
233
234 self.validate_with_context(&ctx).await?;
235
236 Ok(TransferParams {
237 user_id: self.user_id,
238 asset: self.asset,
239 amount: self.amount,
240 spend_id: self.spend_id,
241 comment: self.comment,
242 disable_send_notification: self.disable_send_notification,
243 })
244 }
245}
246
247#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn test_get_transfers_params() {
255 let params = GetTransfersParamsBuilder::new()
256 .asset(CryptoCurrencyCode::Ton)
257 .offset(2)
258 .spend_id("spend_id")
259 .build()
260 .unwrap();
261
262 assert_eq!(params.asset, Some(CryptoCurrencyCode::Ton));
263 assert_eq!(params.offset, Some(2));
264 assert_eq!(params.spend_id, Some("spend_id".to_string()));
265 }
266
267 #[test]
268 fn test_get_transfers_params_invalid_count() {
269 let params = GetTransfersParamsBuilder::new().count(1001).build();
270
271 assert!(matches!(
272 params,
273 Err(CryptoBotError::ValidationError {
274 kind: ValidationErrorKind::Range,
275 field: Some(field),
276 ..
277 }) if field == "count"
278 ));
279 }
280
281 #[tokio::test]
282 async fn test_transfer_params() {
283 let client = CryptoBot::test_client();
284
285 let params = TransferParamsBuilder::new()
286 .user_id(123456789)
287 .asset(CryptoCurrencyCode::Ton)
288 .amount(Decimal::from(100))
289 .spend_id("test_id")
290 .comment("test comment")
291 .disable_send_notification(true)
292 .build(&client)
293 .await
294 .unwrap();
295
296 assert_eq!(params.user_id, 123456789);
297 assert_eq!(params.asset, CryptoCurrencyCode::Ton);
298 assert_eq!(params.amount, Decimal::from(100));
299 assert_eq!(params.spend_id, "test_id");
300 assert_eq!(params.comment, Some("test comment".to_string()));
301 assert_eq!(params.disable_send_notification, Some(true));
302 }
303 #[tokio::test]
304 async fn test_transfer_params_invalid_spend_id() {
305 let client = CryptoBot::test_client();
306
307 let result = TransferParamsBuilder::default()
308 .user_id(123456789)
309 .asset(CryptoCurrencyCode::Ton)
310 .amount(Decimal::from(100))
311 .spend_id("x".repeat(65))
312 .build(&client)
313 .await;
314
315 assert!(matches!(
316 result,
317 Err(CryptoBotError::ValidationError {
318 kind: ValidationErrorKind::Range,
319 field: Some(field),
320 ..
321 }) if field == "spend_id"
322 ));
323 }
324
325 #[tokio::test]
326 async fn test_transfer_params_validate_amount() {
327 let client = CryptoBot::test_client();
328
329 let result = TransferParamsBuilder::new()
330 .user_id(123456789)
331 .asset(CryptoCurrencyCode::Ton)
332 .amount(Decimal::from(100000))
333 .spend_id("test_spend_id")
334 .build(&client)
335 .await;
336
337 assert!(matches!(
338 result,
339 Err(CryptoBotError::ValidationError {
340 kind: ValidationErrorKind::Range,
341 field: Some(field),
342 ..
343 }) if field == "amount"
344 ));
345 }
346
347 #[tokio::test]
348 async fn test_transfer_params_validate_comments() {
349 let client = CryptoBot::test_client();
350
351 let result = TransferParamsBuilder::new()
352 .user_id(123456789)
353 .asset(CryptoCurrencyCode::Ton)
354 .amount(Decimal::from(100))
355 .spend_id("test_spend_id")
356 .comment("x".repeat(1025))
357 .build(&client)
358 .await;
359
360 assert!(matches!(
361 result,
362 Err(CryptoBotError::ValidationError {
363 kind: ValidationErrorKind::Range,
364 field: Some(field),
365 ..
366 }) if field == "comment"
367 ));
368 }
369}