crypto_pay_api/models/check/
builder.rs1use std::marker::PhantomData;
2
3use rust_decimal::Decimal;
4use rust_decimal_macros::dec;
5
6use crate::{
7 api::ExchangeRateAPI,
8 client::CryptoBot,
9 error::{CryptoBotError, CryptoBotResult, ValidationErrorKind},
10 models::{CryptoCurrencyCode, Missing, Set},
11 validation::{validate_amount, validate_count, ContextValidate, FieldValidate, ValidationContext},
12};
13
14use super::{CheckStatus, CreateCheckParams, GetChecksParams};
15
16pub struct CreateCheckParamsBuilder<A = Missing, M = Missing> {
19 asset: CryptoCurrencyCode,
20 amount: Decimal,
21 pin_to_user_id: Option<u64>,
22 pin_to_username: Option<String>,
23 _state: PhantomData<(A, M)>,
24}
25
26impl Default for CreateCheckParamsBuilder {
27 fn default() -> Self {
28 Self {
29 asset: CryptoCurrencyCode::Ton,
30 amount: dec!(0),
31 pin_to_user_id: None,
32 pin_to_username: None,
33 _state: PhantomData,
34 }
35 }
36}
37
38impl CreateCheckParamsBuilder {
39 pub fn new() -> Self {
41 Self::default()
42 }
43}
44
45impl<M> CreateCheckParamsBuilder<Missing, M> {
46 pub fn asset(mut self, asset: CryptoCurrencyCode) -> CreateCheckParamsBuilder<Set, M> {
49 self.asset = asset;
50 self.transform()
51 }
52}
53
54impl<A> CreateCheckParamsBuilder<A, Missing> {
55 pub fn amount(mut self, amount: Decimal) -> CreateCheckParamsBuilder<A, Set> {
58 self.amount = amount;
59 self.transform()
60 }
61}
62
63impl<A, M> CreateCheckParamsBuilder<A, M> {
64 pub fn pin_to_user_id(mut self, pin_to_user_id: u64) -> Self {
67 self.pin_to_user_id = Some(pin_to_user_id);
68 self.transform()
69 }
70
71 pub fn pin_to_username(mut self, pin_to_username: &str) -> Self {
74 self.pin_to_username = Some(pin_to_username.to_string());
75 self
76 }
77
78 fn transform<A2, M2>(self) -> CreateCheckParamsBuilder<A2, M2> {
79 CreateCheckParamsBuilder {
80 asset: self.asset,
81 amount: self.amount,
82 pin_to_user_id: self.pin_to_user_id,
83 pin_to_username: self.pin_to_username,
84 _state: PhantomData,
85 }
86 }
87}
88
89impl FieldValidate for CreateCheckParamsBuilder<Set, Set> {
90 fn validate(&self) -> CryptoBotResult<()> {
91 if self.amount < Decimal::ZERO {
92 return Err(CryptoBotError::ValidationError {
93 kind: ValidationErrorKind::Range,
94 message: "Amount must be greater than 0".to_string(),
95 field: Some("amount".to_string()),
96 });
97 }
98 Ok(())
99 }
100}
101
102#[async_trait::async_trait]
103impl ContextValidate for CreateCheckParamsBuilder<Set, Set> {
104 async fn validate_with_context(&self, ctx: &ValidationContext) -> CryptoBotResult<()> {
105 validate_amount(&self.amount, &self.asset, ctx).await
106 }
107}
108
109impl CreateCheckParamsBuilder<Set, Set> {
110 pub async fn build(self, client: &CryptoBot) -> CryptoBotResult<CreateCheckParams> {
111 self.validate()?;
112
113 let exchange_rates = client.get_exchange_rates().await?;
114
115 let ctx = ValidationContext { exchange_rates };
116
117 self.validate_with_context(&ctx).await?;
118
119 Ok(CreateCheckParams {
120 asset: self.asset,
121 amount: self.amount,
122 pin_to_user_id: self.pin_to_user_id,
123 pin_to_username: self.pin_to_username,
124 })
125 }
126}
127
128#[derive(Debug, Default)]
133pub struct GetChecksParamsBuilder {
134 asset: Option<CryptoCurrencyCode>,
135 check_ids: Option<Vec<u64>>,
136 status: Option<CheckStatus>,
137 offset: Option<u32>,
138 count: Option<u16>,
139}
140
141impl GetChecksParamsBuilder {
142 pub fn new() -> Self {
144 Self::default()
145 }
146
147 pub fn asset(mut self, asset: CryptoCurrencyCode) -> Self {
150 self.asset = Some(asset);
151 self
152 }
153
154 pub fn check_ids(mut self, check_ids: Vec<u64>) -> Self {
156 self.check_ids = Some(check_ids);
157 self
158 }
159
160 pub fn status(mut self, status: CheckStatus) -> Self {
164 self.status = Some(status);
165 self
166 }
167
168 pub fn offset(mut self, offset: u32) -> Self {
172 self.offset = Some(offset);
173 self
174 }
175
176 pub fn count(mut self, count: u16) -> Self {
180 self.count = Some(count);
181 self
182 }
183}
184
185impl FieldValidate for GetChecksParamsBuilder {
186 fn validate(&self) -> CryptoBotResult<()> {
187 if let Some(count) = &self.count {
188 validate_count(*count)?;
189 }
190 Ok(())
191 }
192}
193
194impl GetChecksParamsBuilder {
195 pub fn build(self) -> CryptoBotResult<GetChecksParams> {
196 self.validate()?;
197
198 Ok(GetChecksParams {
199 asset: self.asset,
200 check_ids: self.check_ids,
201 status: self.status,
202 offset: self.offset,
203 count: self.count,
204 })
205 }
206}
207#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_get_checks_params_builder() {
215 let params = GetChecksParamsBuilder::new()
216 .count(5)
217 .asset(CryptoCurrencyCode::Ton)
218 .status(CheckStatus::Activated)
219 .offset(10)
220 .build()
221 .unwrap();
222 assert_eq!(params.count, Some(5));
223 assert_eq!(params.asset, Some(CryptoCurrencyCode::Ton));
224 assert_eq!(params.status, Some(CheckStatus::Activated));
225 assert_eq!(params.offset, Some(10));
226 }
227
228 #[test]
229 fn test_get_checks_params_builder_invalid_count() {
230 let params = GetChecksParamsBuilder::new().count(1001).build();
231 assert!(matches!(
232 params,
233 Err(CryptoBotError::ValidationError {
234 kind: ValidationErrorKind::Range,
235 field: Some(field),
236 ..
237 }) if field == "count"
238 ));
239 }
240
241 #[tokio::test]
242 async fn test_create_check_params_builder() {
243 let client = CryptoBot::test_client();
244
245 let params = CreateCheckParamsBuilder::new()
246 .asset(CryptoCurrencyCode::Ton)
247 .amount(Decimal::from(100))
248 .pin_to_user_id(123456789)
249 .pin_to_username("test_username")
250 .build(&client)
251 .await
252 .unwrap();
253
254 assert_eq!(params.asset, CryptoCurrencyCode::Ton);
255 assert_eq!(params.amount, Decimal::from(100));
256 assert_eq!(params.pin_to_user_id, Some(123456789));
257 assert_eq!(params.pin_to_username, Some("test_username".to_string()));
258 }
259
260 #[tokio::test]
261 async fn test_create_check_params_builder_invalid_amount() {
262 let client = CryptoBot::test_client();
263 let result = CreateCheckParamsBuilder::new()
264 .asset(CryptoCurrencyCode::Ton)
265 .amount(Decimal::from(-100))
266 .build(&client)
267 .await;
268
269 assert!(matches!(
270 result,
271 Err(CryptoBotError::ValidationError {
272 kind: ValidationErrorKind::Range,
273 field: Some(field),
274 ..
275 }) if field == "amount"
276 ));
277 }
278}