1#![allow(unused_imports)]
15use async_trait::async_trait;
16use derive_builder::Builder;
17use reqwest;
18use rust_decimal::prelude::*;
19use serde::{Deserialize, Serialize};
20use serde_json::{Value, json};
21use std::collections::BTreeMap;
22
23use crate::common::{
24 config::ConfigurationRestApi,
25 models::{ParamBuildError, RestApiResponse},
26 utils::send_request,
27};
28use crate::stocks::rest_api::models;
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait TokenizedApi: Send + Sync {
34 async fn tokenized_convert_history(
35 &self,
36 params: TokenizedConvertHistoryParams,
37 ) -> anyhow::Result<RestApiResponse<models::TokenizedConvertHistoryResponse>>;
38 async fn tokenized_convert_status(
39 &self,
40 params: TokenizedConvertStatusParams,
41 ) -> anyhow::Result<RestApiResponse<models::TokenizedConvertStatusResponse>>;
42 async fn tokenized_mint(
43 &self,
44 params: TokenizedMintParams,
45 ) -> anyhow::Result<RestApiResponse<models::TokenizedMintResponse>>;
46 async fn tokenized_redeem(
47 &self,
48 params: TokenizedRedeemParams,
49 ) -> anyhow::Result<RestApiResponse<models::TokenizedRedeemResponse>>;
50}
51
52#[derive(Debug, Clone)]
53pub struct TokenizedApiClient {
54 configuration: ConfigurationRestApi,
55}
56
57impl TokenizedApiClient {
58 pub fn new(configuration: ConfigurationRestApi) -> Self {
59 Self { configuration }
60 }
61}
62
63#[allow(non_camel_case_types)]
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub enum TokenizedConvertStatusConvertTypeEnum {
66 #[serde(rename = "MINT")]
67 Mint,
68 #[serde(rename = "REDEEM")]
69 Redeem,
70}
71
72impl TokenizedConvertStatusConvertTypeEnum {
73 #[must_use]
74 pub fn as_str(&self) -> &'static str {
75 match self {
76 Self::Mint => "MINT",
77 Self::Redeem => "REDEEM",
78 }
79 }
80}
81
82impl std::str::FromStr for TokenizedConvertStatusConvertTypeEnum {
83 type Err = Box<dyn std::error::Error + Send + Sync>;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 match s {
87 "MINT" => Ok(Self::Mint),
88 "REDEEM" => Ok(Self::Redeem),
89 other => {
90 Err(format!("invalid TokenizedConvertStatusConvertTypeEnum: {}", other).into())
91 }
92 }
93 }
94}
95
96#[derive(Clone, Debug, Builder, Deserialize, Default)]
101#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
102pub struct TokenizedConvertHistoryParams {
103 #[builder(setter(into), default)]
107 #[serde(rename = "startTime", default)]
108 pub start_time: Option<i64>,
109 #[builder(setter(into), default)]
113 #[serde(rename = "endTime", default)]
114 pub end_time: Option<i64>,
115 #[builder(setter(into), default)]
119 #[serde(rename = "lastId", default)]
120 pub last_id: Option<i64>,
121 #[builder(setter(into), default)]
125 #[serde(rename = "size", default)]
126 pub size: Option<i32>,
127 #[builder(setter(into), default)]
131 #[serde(rename = "recvWindow", default)]
132 pub recv_window: Option<i64>,
133}
134
135impl TokenizedConvertHistoryParams {
136 #[must_use]
139 pub fn builder() -> TokenizedConvertHistoryParamsBuilder {
140 TokenizedConvertHistoryParamsBuilder::default()
141 }
142}
143#[derive(Clone, Debug, Builder, Deserialize)]
148#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
149pub struct TokenizedConvertStatusParams {
150 #[builder(setter(into))]
154 #[serde(rename = "issuerRequestId")]
155 pub issuer_request_id: String,
156 #[builder(setter(into))]
160 #[serde(rename = "convertType")]
161 pub convert_type: TokenizedConvertStatusConvertTypeEnum,
162 #[builder(setter(into), default)]
166 #[serde(rename = "recvWindow", default)]
167 pub recv_window: Option<i64>,
168}
169
170impl TokenizedConvertStatusParams {
171 #[must_use]
179 pub fn builder(
180 issuer_request_id: String,
181 convert_type: TokenizedConvertStatusConvertTypeEnum,
182 ) -> TokenizedConvertStatusParamsBuilder {
183 TokenizedConvertStatusParamsBuilder::default()
184 .issuer_request_id(issuer_request_id)
185 .convert_type(convert_type)
186 }
187}
188#[derive(Clone, Debug, Builder, Deserialize)]
193#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
194pub struct TokenizedMintParams {
195 #[builder(setter(into))]
199 #[serde(rename = "underlyingAsset")]
200 pub underlying_asset: String,
201 #[builder(setter(into))]
205 #[serde(rename = "underlyingAssetAmount")]
206 pub underlying_asset_amount: String,
207 #[builder(setter(into), default)]
211 #[serde(rename = "clientOrderId", default)]
212 pub client_order_id: Option<String>,
213 #[builder(setter(into), default)]
217 #[serde(rename = "recvWindow", default)]
218 pub recv_window: Option<i64>,
219}
220
221impl TokenizedMintParams {
222 #[must_use]
230 pub fn builder(
231 underlying_asset: String,
232 underlying_asset_amount: String,
233 ) -> TokenizedMintParamsBuilder {
234 TokenizedMintParamsBuilder::default()
235 .underlying_asset(underlying_asset)
236 .underlying_asset_amount(underlying_asset_amount)
237 }
238}
239#[derive(Clone, Debug, Builder, Deserialize)]
244#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
245pub struct TokenizedRedeemParams {
246 #[builder(setter(into))]
250 #[serde(rename = "tokenizedAsset")]
251 pub tokenized_asset: String,
252 #[builder(setter(into))]
256 #[serde(rename = "tokenizedAssetAmount")]
257 pub tokenized_asset_amount: String,
258 #[builder(setter(into), default)]
262 #[serde(rename = "clientOrderId", default)]
263 pub client_order_id: Option<String>,
264 #[builder(setter(into), default)]
268 #[serde(rename = "recvWindow", default)]
269 pub recv_window: Option<i64>,
270}
271
272impl TokenizedRedeemParams {
273 #[must_use]
281 pub fn builder(
282 tokenized_asset: String,
283 tokenized_asset_amount: String,
284 ) -> TokenizedRedeemParamsBuilder {
285 TokenizedRedeemParamsBuilder::default()
286 .tokenized_asset(tokenized_asset)
287 .tokenized_asset_amount(tokenized_asset_amount)
288 }
289}
290
291#[async_trait]
292impl TokenizedApi for TokenizedApiClient {
293 async fn tokenized_convert_history(
294 &self,
295 params: TokenizedConvertHistoryParams,
296 ) -> anyhow::Result<RestApiResponse<models::TokenizedConvertHistoryResponse>> {
297 let TokenizedConvertHistoryParams {
298 start_time,
299 end_time,
300 last_id,
301 size,
302 recv_window,
303 } = params;
304
305 let mut query_params = BTreeMap::new();
306 let body_params = BTreeMap::new();
307
308 if let Some(rw) = start_time {
309 query_params.insert("startTime".to_string(), json!(rw));
310 }
311
312 if let Some(rw) = end_time {
313 query_params.insert("endTime".to_string(), json!(rw));
314 }
315
316 if let Some(rw) = last_id {
317 query_params.insert("lastId".to_string(), json!(rw));
318 }
319
320 if let Some(rw) = size {
321 query_params.insert("size".to_string(), json!(rw));
322 }
323
324 if let Some(rw) = recv_window {
325 query_params.insert("recvWindow".to_string(), json!(rw));
326 }
327
328 send_request::<models::TokenizedConvertHistoryResponse>(
329 &self.configuration,
330 "/sapi/v1/equity/tokenized/history",
331 reqwest::Method::GET,
332 query_params,
333 body_params,
334 if HAS_TIME_UNIT {
335 self.configuration.time_unit
336 } else {
337 None
338 },
339 true,
340 )
341 .await
342 }
343
344 async fn tokenized_convert_status(
345 &self,
346 params: TokenizedConvertStatusParams,
347 ) -> anyhow::Result<RestApiResponse<models::TokenizedConvertStatusResponse>> {
348 let TokenizedConvertStatusParams {
349 issuer_request_id,
350 convert_type,
351 recv_window,
352 } = params;
353
354 let mut query_params = BTreeMap::new();
355 let body_params = BTreeMap::new();
356
357 query_params.insert("issuerRequestId".to_string(), json!(issuer_request_id));
358
359 query_params.insert("convertType".to_string(), json!(convert_type));
360
361 if let Some(rw) = recv_window {
362 query_params.insert("recvWindow".to_string(), json!(rw));
363 }
364
365 send_request::<models::TokenizedConvertStatusResponse>(
366 &self.configuration,
367 "/sapi/v1/equity/tokenized/convert-status",
368 reqwest::Method::GET,
369 query_params,
370 body_params,
371 if HAS_TIME_UNIT {
372 self.configuration.time_unit
373 } else {
374 None
375 },
376 true,
377 )
378 .await
379 }
380
381 async fn tokenized_mint(
382 &self,
383 params: TokenizedMintParams,
384 ) -> anyhow::Result<RestApiResponse<models::TokenizedMintResponse>> {
385 let TokenizedMintParams {
386 underlying_asset,
387 underlying_asset_amount,
388 client_order_id,
389 recv_window,
390 } = params;
391
392 let mut query_params = BTreeMap::new();
393 let body_params = BTreeMap::new();
394
395 query_params.insert("underlyingAsset".to_string(), json!(underlying_asset));
396
397 query_params.insert(
398 "underlyingAssetAmount".to_string(),
399 json!(underlying_asset_amount),
400 );
401
402 if let Some(rw) = client_order_id {
403 query_params.insert("clientOrderId".to_string(), json!(rw));
404 }
405
406 if let Some(rw) = recv_window {
407 query_params.insert("recvWindow".to_string(), json!(rw));
408 }
409
410 send_request::<models::TokenizedMintResponse>(
411 &self.configuration,
412 "/sapi/v1/equity/tokenized/mint",
413 reqwest::Method::POST,
414 query_params,
415 body_params,
416 if HAS_TIME_UNIT {
417 self.configuration.time_unit
418 } else {
419 None
420 },
421 true,
422 )
423 .await
424 }
425
426 async fn tokenized_redeem(
427 &self,
428 params: TokenizedRedeemParams,
429 ) -> anyhow::Result<RestApiResponse<models::TokenizedRedeemResponse>> {
430 let TokenizedRedeemParams {
431 tokenized_asset,
432 tokenized_asset_amount,
433 client_order_id,
434 recv_window,
435 } = params;
436
437 let mut query_params = BTreeMap::new();
438 let body_params = BTreeMap::new();
439
440 query_params.insert("tokenizedAsset".to_string(), json!(tokenized_asset));
441
442 query_params.insert(
443 "tokenizedAssetAmount".to_string(),
444 json!(tokenized_asset_amount),
445 );
446
447 if let Some(rw) = client_order_id {
448 query_params.insert("clientOrderId".to_string(), json!(rw));
449 }
450
451 if let Some(rw) = recv_window {
452 query_params.insert("recvWindow".to_string(), json!(rw));
453 }
454
455 send_request::<models::TokenizedRedeemResponse>(
456 &self.configuration,
457 "/sapi/v1/equity/tokenized/redeem",
458 reqwest::Method::POST,
459 query_params,
460 body_params,
461 if HAS_TIME_UNIT {
462 self.configuration.time_unit
463 } else {
464 None
465 },
466 true,
467 )
468 .await
469 }
470}
471
472#[cfg(all(test, feature = "stocks"))]
473mod tests {
474 use super::*;
475 use crate::TOKIO_SHARED_RT;
476 use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
477 use async_trait::async_trait;
478 use std::collections::HashMap;
479
480 struct DummyRestApiResponse<T> {
481 inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
482 status: u16,
483 headers: HashMap<String, String>,
484 rate_limits: Option<Vec<RestApiRateLimit>>,
485 }
486
487 impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
488 fn from(dummy: DummyRestApiResponse<T>) -> Self {
489 Self {
490 data_fn: dummy.inner,
491 status: dummy.status,
492 headers: dummy.headers,
493 rate_limits: dummy.rate_limits,
494 }
495 }
496 }
497
498 struct MockTokenizedApiClient {
499 force_error: bool,
500 }
501
502 #[async_trait]
503 impl TokenizedApi for MockTokenizedApiClient {
504 async fn tokenized_convert_history(
505 &self,
506 _params: TokenizedConvertHistoryParams,
507 ) -> anyhow::Result<RestApiResponse<models::TokenizedConvertHistoryResponse>> {
508 if self.force_error {
509 return Err(ConnectorError::ConnectorClientError {
510 msg: "ResponseError".to_string(),
511 code: None,
512 }
513 .into());
514 }
515
516 let resp_json: Value = serde_json::from_str(r#"{"rows":[{"underlyingAsset":"AAPL","underlyingAssetAmount":"1.000000000","tokenizedAsset":"AAPLB","tokenizedAssetAmount":"1.000000000","issuerRequestId":"mint-20260505-8f3b9e1a2d3c4b5a","convertType":"MINT","status":"S","createdAt":1735900000000,"updatedAt":1735900060000}],"hasMore":true,"nextLastId":10020}"#).unwrap_or_else(|_| serde_json::json!({}));
517 let dummy_response: models::TokenizedConvertHistoryResponse =
518 serde_json::from_value(resp_json.clone())
519 .expect("should parse into models::TokenizedConvertHistoryResponse");
520
521 let dummy = DummyRestApiResponse {
522 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
523 status: 200,
524 headers: HashMap::new(),
525 rate_limits: None,
526 };
527
528 Ok(dummy.into())
529 }
530
531 async fn tokenized_convert_status(
532 &self,
533 _params: TokenizedConvertStatusParams,
534 ) -> anyhow::Result<RestApiResponse<models::TokenizedConvertStatusResponse>> {
535 if self.force_error {
536 return Err(ConnectorError::ConnectorClientError {
537 msg: "ResponseError".to_string(),
538 code: None,
539 }
540 .into());
541 }
542
543 let resp_json: Value = serde_json::from_str(r#"{"underlyingAsset":"AAPL","underlyingAssetAmount":"1.000000000","tokenizedAsset":"AAPLB","tokenizedAssetAmount":"1.000000000","issuerRequestId":"mint-20260505-8f3b9e1a2d3c4b5a","convertType":"MINT","status":"S","createdAt":1735900000000,"updatedAt":1735900060000}"#).unwrap_or_else(|_| serde_json::json!({}));
544 let dummy_response: models::TokenizedConvertStatusResponse =
545 serde_json::from_value(resp_json.clone())
546 .expect("should parse into models::TokenizedConvertStatusResponse");
547
548 let dummy = DummyRestApiResponse {
549 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
550 status: 200,
551 headers: HashMap::new(),
552 rate_limits: None,
553 };
554
555 Ok(dummy.into())
556 }
557
558 async fn tokenized_mint(
559 &self,
560 _params: TokenizedMintParams,
561 ) -> anyhow::Result<RestApiResponse<models::TokenizedMintResponse>> {
562 if self.force_error {
563 return Err(ConnectorError::ConnectorClientError {
564 msg: "ResponseError".to_string(),
565 code: None,
566 }
567 .into());
568 }
569
570 let resp_json: Value = serde_json::from_str(
571 r#"{"issuerRequestId":"mint-20260505-8f3b9e1a2d3c4b5a","status":"P"}"#,
572 )
573 .unwrap_or_else(|_| serde_json::json!({}));
574 let dummy_response: models::TokenizedMintResponse =
575 serde_json::from_value(resp_json.clone())
576 .expect("should parse into models::TokenizedMintResponse");
577
578 let dummy = DummyRestApiResponse {
579 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
580 status: 200,
581 headers: HashMap::new(),
582 rate_limits: None,
583 };
584
585 Ok(dummy.into())
586 }
587
588 async fn tokenized_redeem(
589 &self,
590 _params: TokenizedRedeemParams,
591 ) -> anyhow::Result<RestApiResponse<models::TokenizedRedeemResponse>> {
592 if self.force_error {
593 return Err(ConnectorError::ConnectorClientError {
594 msg: "ResponseError".to_string(),
595 code: None,
596 }
597 .into());
598 }
599
600 let resp_json: Value = serde_json::from_str(
601 r#"{"issuerRequestId":"redeem-20260505-a1b2c3d4e5f6","status":"P"}"#,
602 )
603 .unwrap_or_else(|_| serde_json::json!({}));
604 let dummy_response: models::TokenizedRedeemResponse =
605 serde_json::from_value(resp_json.clone())
606 .expect("should parse into models::TokenizedRedeemResponse");
607
608 let dummy = DummyRestApiResponse {
609 inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
610 status: 200,
611 headers: HashMap::new(),
612 rate_limits: None,
613 };
614
615 Ok(dummy.into())
616 }
617 }
618
619 #[test]
620 fn tokenized_convert_history_required_params_success() {
621 TOKIO_SHARED_RT.block_on(async {
622 let client = MockTokenizedApiClient { force_error: false };
623
624 let params = TokenizedConvertHistoryParams::builder().build().unwrap();
625
626 let resp_json: Value = serde_json::from_str(r#"{"rows":[{"underlyingAsset":"AAPL","underlyingAssetAmount":"1.000000000","tokenizedAsset":"AAPLB","tokenizedAssetAmount":"1.000000000","issuerRequestId":"mint-20260505-8f3b9e1a2d3c4b5a","convertType":"MINT","status":"S","createdAt":1735900000000,"updatedAt":1735900060000}],"hasMore":true,"nextLastId":10020}"#).unwrap_or_else(|_| serde_json::json!({}));
627 let expected_response : models::TokenizedConvertHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::TokenizedConvertHistoryResponse");
628
629 let resp = client.tokenized_convert_history(params).await.expect("Expected a response");
630 let data_future = resp.data();
631 let actual_response = data_future.await.unwrap();
632 assert_eq!(actual_response, expected_response);
633 });
634 }
635
636 #[test]
637 fn tokenized_convert_history_optional_params_success() {
638 TOKIO_SHARED_RT.block_on(async {
639 let client = MockTokenizedApiClient { force_error: false };
640
641 let params = TokenizedConvertHistoryParams::builder().start_time(1735800000000).end_time(1735900000000).last_id(10019).size(20).recv_window(5000).build().unwrap();
642
643 let resp_json: Value = serde_json::from_str(r#"{"rows":[{"underlyingAsset":"AAPL","underlyingAssetAmount":"1.000000000","tokenizedAsset":"AAPLB","tokenizedAssetAmount":"1.000000000","issuerRequestId":"mint-20260505-8f3b9e1a2d3c4b5a","convertType":"MINT","status":"S","createdAt":1735900000000,"updatedAt":1735900060000}],"hasMore":true,"nextLastId":10020}"#).unwrap_or_else(|_| serde_json::json!({}));
644 let expected_response : models::TokenizedConvertHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::TokenizedConvertHistoryResponse");
645
646 let resp = client.tokenized_convert_history(params).await.expect("Expected a response");
647 let data_future = resp.data();
648 let actual_response = data_future.await.unwrap();
649 assert_eq!(actual_response, expected_response);
650 });
651 }
652
653 #[test]
654 fn tokenized_convert_history_response_error() {
655 TOKIO_SHARED_RT.block_on(async {
656 let client = MockTokenizedApiClient { force_error: true };
657
658 let params = TokenizedConvertHistoryParams::builder().build().unwrap();
659
660 match client.tokenized_convert_history(params).await {
661 Ok(_) => panic!("Expected an error"),
662 Err(err) => {
663 assert_eq!(err.to_string(), "Connector client error: ResponseError");
664 }
665 }
666 });
667 }
668
669 #[test]
670 fn tokenized_convert_status_required_params_success() {
671 TOKIO_SHARED_RT.block_on(async {
672 let client = MockTokenizedApiClient { force_error: false };
673
674 let params = TokenizedConvertStatusParams::builder("mint-20260505-8f3b9e1a2d3c4b5a".to_string(),TokenizedConvertStatusConvertTypeEnum::Mint,).build().unwrap();
675
676 let resp_json: Value = serde_json::from_str(r#"{"underlyingAsset":"AAPL","underlyingAssetAmount":"1.000000000","tokenizedAsset":"AAPLB","tokenizedAssetAmount":"1.000000000","issuerRequestId":"mint-20260505-8f3b9e1a2d3c4b5a","convertType":"MINT","status":"S","createdAt":1735900000000,"updatedAt":1735900060000}"#).unwrap_or_else(|_| serde_json::json!({}));
677 let expected_response : models::TokenizedConvertStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::TokenizedConvertStatusResponse");
678
679 let resp = client.tokenized_convert_status(params).await.expect("Expected a response");
680 let data_future = resp.data();
681 let actual_response = data_future.await.unwrap();
682 assert_eq!(actual_response, expected_response);
683 });
684 }
685
686 #[test]
687 fn tokenized_convert_status_optional_params_success() {
688 TOKIO_SHARED_RT.block_on(async {
689 let client = MockTokenizedApiClient { force_error: false };
690
691 let params = TokenizedConvertStatusParams::builder("mint-20260505-8f3b9e1a2d3c4b5a".to_string(),TokenizedConvertStatusConvertTypeEnum::Mint,).recv_window(5000).build().unwrap();
692
693 let resp_json: Value = serde_json::from_str(r#"{"underlyingAsset":"AAPL","underlyingAssetAmount":"1.000000000","tokenizedAsset":"AAPLB","tokenizedAssetAmount":"1.000000000","issuerRequestId":"mint-20260505-8f3b9e1a2d3c4b5a","convertType":"MINT","status":"S","createdAt":1735900000000,"updatedAt":1735900060000}"#).unwrap_or_else(|_| serde_json::json!({}));
694 let expected_response : models::TokenizedConvertStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::TokenizedConvertStatusResponse");
695
696 let resp = client.tokenized_convert_status(params).await.expect("Expected a response");
697 let data_future = resp.data();
698 let actual_response = data_future.await.unwrap();
699 assert_eq!(actual_response, expected_response);
700 });
701 }
702
703 #[test]
704 fn tokenized_convert_status_response_error() {
705 TOKIO_SHARED_RT.block_on(async {
706 let client = MockTokenizedApiClient { force_error: true };
707
708 let params = TokenizedConvertStatusParams::builder(
709 "mint-20260505-8f3b9e1a2d3c4b5a".to_string(),
710 TokenizedConvertStatusConvertTypeEnum::Mint,
711 )
712 .build()
713 .unwrap();
714
715 match client.tokenized_convert_status(params).await {
716 Ok(_) => panic!("Expected an error"),
717 Err(err) => {
718 assert_eq!(err.to_string(), "Connector client error: ResponseError");
719 }
720 }
721 });
722 }
723
724 #[test]
725 fn tokenized_mint_required_params_success() {
726 TOKIO_SHARED_RT.block_on(async {
727 let client = MockTokenizedApiClient { force_error: false };
728
729 let params = TokenizedMintParams::builder("AAPL".to_string(), "1".to_string())
730 .build()
731 .unwrap();
732
733 let resp_json: Value = serde_json::from_str(
734 r#"{"issuerRequestId":"mint-20260505-8f3b9e1a2d3c4b5a","status":"P"}"#,
735 )
736 .unwrap_or_else(|_| serde_json::json!({}));
737 let expected_response: models::TokenizedMintResponse =
738 serde_json::from_value(resp_json.clone())
739 .expect("should parse into models::TokenizedMintResponse");
740
741 let resp = client
742 .tokenized_mint(params)
743 .await
744 .expect("Expected a response");
745 let data_future = resp.data();
746 let actual_response = data_future.await.unwrap();
747 assert_eq!(actual_response, expected_response);
748 });
749 }
750
751 #[test]
752 fn tokenized_mint_optional_params_success() {
753 TOKIO_SHARED_RT.block_on(async {
754 let client = MockTokenizedApiClient { force_error: false };
755
756 let params = TokenizedMintParams::builder("AAPL".to_string(), "1".to_string())
757 .client_order_id("mint-client-id-32chars-0000000001".to_string())
758 .recv_window(5000)
759 .build()
760 .unwrap();
761
762 let resp_json: Value = serde_json::from_str(
763 r#"{"issuerRequestId":"mint-20260505-8f3b9e1a2d3c4b5a","status":"P"}"#,
764 )
765 .unwrap_or_else(|_| serde_json::json!({}));
766 let expected_response: models::TokenizedMintResponse =
767 serde_json::from_value(resp_json.clone())
768 .expect("should parse into models::TokenizedMintResponse");
769
770 let resp = client
771 .tokenized_mint(params)
772 .await
773 .expect("Expected a response");
774 let data_future = resp.data();
775 let actual_response = data_future.await.unwrap();
776 assert_eq!(actual_response, expected_response);
777 });
778 }
779
780 #[test]
781 fn tokenized_mint_response_error() {
782 TOKIO_SHARED_RT.block_on(async {
783 let client = MockTokenizedApiClient { force_error: true };
784
785 let params = TokenizedMintParams::builder("AAPL".to_string(), "1".to_string())
786 .build()
787 .unwrap();
788
789 match client.tokenized_mint(params).await {
790 Ok(_) => panic!("Expected an error"),
791 Err(err) => {
792 assert_eq!(err.to_string(), "Connector client error: ResponseError");
793 }
794 }
795 });
796 }
797
798 #[test]
799 fn tokenized_redeem_required_params_success() {
800 TOKIO_SHARED_RT.block_on(async {
801 let client = MockTokenizedApiClient { force_error: false };
802
803 let params = TokenizedRedeemParams::builder("AAPLB".to_string(), "1".to_string())
804 .build()
805 .unwrap();
806
807 let resp_json: Value = serde_json::from_str(
808 r#"{"issuerRequestId":"redeem-20260505-a1b2c3d4e5f6","status":"P"}"#,
809 )
810 .unwrap_or_else(|_| serde_json::json!({}));
811 let expected_response: models::TokenizedRedeemResponse =
812 serde_json::from_value(resp_json.clone())
813 .expect("should parse into models::TokenizedRedeemResponse");
814
815 let resp = client
816 .tokenized_redeem(params)
817 .await
818 .expect("Expected a response");
819 let data_future = resp.data();
820 let actual_response = data_future.await.unwrap();
821 assert_eq!(actual_response, expected_response);
822 });
823 }
824
825 #[test]
826 fn tokenized_redeem_optional_params_success() {
827 TOKIO_SHARED_RT.block_on(async {
828 let client = MockTokenizedApiClient { force_error: false };
829
830 let params = TokenizedRedeemParams::builder("AAPLB".to_string(), "1".to_string())
831 .client_order_id("redeem-client-id-32chars-000000001".to_string())
832 .recv_window(5000)
833 .build()
834 .unwrap();
835
836 let resp_json: Value = serde_json::from_str(
837 r#"{"issuerRequestId":"redeem-20260505-a1b2c3d4e5f6","status":"P"}"#,
838 )
839 .unwrap_or_else(|_| serde_json::json!({}));
840 let expected_response: models::TokenizedRedeemResponse =
841 serde_json::from_value(resp_json.clone())
842 .expect("should parse into models::TokenizedRedeemResponse");
843
844 let resp = client
845 .tokenized_redeem(params)
846 .await
847 .expect("Expected a response");
848 let data_future = resp.data();
849 let actual_response = data_future.await.unwrap();
850 assert_eq!(actual_response, expected_response);
851 });
852 }
853
854 #[test]
855 fn tokenized_redeem_response_error() {
856 TOKIO_SHARED_RT.block_on(async {
857 let client = MockTokenizedApiClient { force_error: true };
858
859 let params = TokenizedRedeemParams::builder("AAPLB".to_string(), "1".to_string())
860 .build()
861 .unwrap();
862
863 match client.tokenized_redeem(params).await {
864 Ok(_) => panic!("Expected an error"),
865 Err(err) => {
866 assert_eq!(err.to_string(), "Connector client error: ResponseError");
867 }
868 }
869 });
870 }
871}