deribit_base/model/
response.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 21/7/25
5******************************************************************************/
6use crate::model::order::OrderInfo;
7use crate::model::order_management::QuoteResult;
8use crate::model::trade::{LastTrade, TradeExecution};
9use pretty_simple_display::{DebugPretty, DisplaySimple};
10
11use serde::{Deserialize, Serialize};
12
13/// Generic JSON-RPC 2.0 response wrapper
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct JsonRpcResponse<T> {
16    /// JSON-RPC version
17    pub jsonrpc: String,
18    /// Request ID
19    pub id: Option<serde_json::Value>,
20    /// Result data (present on success)
21    pub result: Option<T>,
22    /// Error information (present on error)
23    pub error: Option<JsonRpcError>,
24    /// Test net flag
25    pub testnet: Option<bool>,
26    /// Use server time
27    #[serde(rename = "usIn")]
28    pub us_in: Option<i64>,
29    /// Use out time
30    #[serde(rename = "usOut")]
31    pub us_out: Option<i64>,
32    /// Use diff time
33    #[serde(rename = "usDiff")]
34    pub us_diff: Option<i64>,
35}
36
37impl<T> JsonRpcResponse<T> {
38    /// Create a successful response
39    pub fn success(id: Option<serde_json::Value>, result: T) -> Self {
40        Self {
41            jsonrpc: "2.0".to_string(),
42            id,
43            result: Some(result),
44            error: None,
45            testnet: None,
46            us_in: None,
47            us_out: None,
48            us_diff: None,
49        }
50    }
51
52    /// Create an error response
53    pub fn error(id: Option<serde_json::Value>, error: JsonRpcError) -> Self {
54        Self {
55            jsonrpc: "2.0".to_string(),
56            id,
57            result: None,
58            error: Some(error),
59            testnet: None,
60            us_in: None,
61            us_out: None,
62            us_diff: None,
63        }
64    }
65
66    /// Check if the response is successful
67    pub fn is_success(&self) -> bool {
68        self.error.is_none() && self.result.is_some()
69    }
70
71    /// Check if the response is an error
72    pub fn is_error(&self) -> bool {
73        self.error.is_some()
74    }
75
76    /// Get the result, consuming the response
77    pub fn into_result(self) -> Result<T, JsonRpcError> {
78        match (self.result, self.error) {
79            (Some(result), None) => Ok(result),
80            (None, Some(error)) => Err(error),
81            (Some(_), Some(error)) => Err(error), // Error takes precedence
82            (None, None) => Err(JsonRpcError {
83                code: -32603,
84                message: "Internal error: neither result nor error present".to_string(),
85                data: None,
86            }),
87        }
88    }
89}
90
91/// JSON-RPC error information
92#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, PartialEq)]
93pub struct JsonRpcError {
94    /// Error code
95    pub code: i32,
96    /// Error message
97    pub message: String,
98    /// Additional error data
99    pub data: Option<serde_json::Value>,
100}
101
102impl JsonRpcError {
103    /// Create a new JSON-RPC error
104    pub fn new(code: i32, message: String) -> Self {
105        Self {
106            code,
107            message,
108            data: None,
109        }
110    }
111
112    /// Create an error with additional data
113    pub fn with_data(code: i32, message: String, data: serde_json::Value) -> Self {
114        Self {
115            code,
116            message,
117            data: Some(data),
118        }
119    }
120
121    /// Parse error (-32700)
122    pub fn parse_error() -> Self {
123        Self::new(-32700, "Parse error".to_string())
124    }
125
126    /// Invalid request (-32600)
127    pub fn invalid_request() -> Self {
128        Self::new(-32600, "Invalid Request".to_string())
129    }
130
131    /// Method not found (-32601)
132    pub fn method_not_found() -> Self {
133        Self::new(-32601, "Method not found".to_string())
134    }
135
136    /// Invalid params (-32602)
137    pub fn invalid_params() -> Self {
138        Self::new(-32602, "Invalid params".to_string())
139    }
140
141    /// Internal error (-32603)
142    pub fn internal_error() -> Self {
143        Self::new(-32603, "Internal error".to_string())
144    }
145
146    /// Check if this is a server error (code between -32099 and -32000)
147    pub fn is_server_error(&self) -> bool {
148        self.code <= -32000 && self.code >= -32099
149    }
150
151    /// Check if this is an application error (code > -32000)
152    pub fn is_application_error(&self) -> bool {
153        self.code > -32000
154    }
155}
156
157/// Authentication response
158#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
159pub struct AuthResponse {
160    /// Access token
161    pub access_token: String,
162    /// Token type (usually "bearer")
163    pub token_type: String,
164    /// Expires in seconds
165    pub expires_in: i64,
166    /// Refresh token
167    pub refresh_token: String,
168    /// Scope
169    pub scope: String,
170}
171
172/// Pagination information
173#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize, PartialEq)]
174pub struct Pagination {
175    /// Current page
176    pub page: Option<u32>,
177    /// Items per page
178    pub per_page: Option<u32>,
179    /// Total items
180    pub total: Option<u64>,
181    /// Total pages
182    pub pages: Option<u32>,
183    /// Has more pages
184    pub has_more: Option<bool>,
185}
186
187/// Generic paginated response
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct PaginatedResponse<T> {
190    /// Data items
191    pub data: Vec<T>,
192    /// Pagination information
193    pub pagination: Option<Pagination>,
194}
195
196impl<T> PaginatedResponse<T> {
197    /// Create a new paginated response
198    pub fn new(data: Vec<T>) -> Self {
199        Self {
200            data,
201            pagination: None,
202        }
203    }
204
205    /// Create a paginated response with pagination info
206    pub fn with_pagination(data: Vec<T>, pagination: Pagination) -> Self {
207        Self {
208            data,
209            pagination: Some(pagination),
210        }
211    }
212
213    /// Check if there are more pages
214    pub fn has_more(&self) -> bool {
215        self.pagination
216            .as_ref()
217            .and_then(|p| p.has_more)
218            .unwrap_or(false)
219    }
220
221    /// Get the number of items
222    pub fn len(&self) -> usize {
223        self.data.len()
224    }
225
226    /// Check if the response is empty
227    pub fn is_empty(&self) -> bool {
228        self.data.is_empty()
229    }
230}
231
232/// WebSocket notification
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct Notification<T> {
235    /// JSON-RPC version
236    pub jsonrpc: String,
237    /// Method name
238    pub method: String,
239    /// Parameters/data
240    pub params: T,
241}
242
243impl<T> Notification<T> {
244    /// Create a new notification
245    pub fn new(method: String, params: T) -> Self {
246        Self {
247            jsonrpc: "2.0".to_string(),
248            method,
249            params,
250        }
251    }
252}
253
254/// Subscription response
255#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
256pub struct SubscriptionResponse {
257    /// Subscription ID
258    pub subscription: String,
259    /// Channel name
260    pub channel: String,
261}
262
263/// Heartbeat response
264#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
265pub struct HeartbeatResponse {
266    /// Type (always "heartbeat")
267    #[serde(rename = "type")]
268    pub type_: String,
269}
270
271/// Test response for connectivity checks
272#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
273pub struct TestResponse {
274    /// Version information
275    pub version: String,
276}
277
278/// Server time response
279#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
280pub struct ServerTimeResponse {
281    /// Current server timestamp in milliseconds
282    pub timestamp: i64,
283}
284
285/// Settlements response structure
286#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
287pub struct SettlementsResponse {
288    /// Continuation token for pagination
289    pub continuation: Option<String>,
290    /// List of settlement events
291    pub settlements: Vec<crate::model::settlement::Settlement>,
292}
293
294impl SettlementsResponse {
295    /// Create a new settlements response
296    pub fn new(settlements: Vec<crate::model::settlement::Settlement>) -> Self {
297        Self {
298            continuation: None,
299            settlements,
300        }
301    }
302
303    /// Create settlements response with continuation token
304    pub fn with_continuation(
305        settlements: Vec<crate::model::settlement::Settlement>,
306        continuation: String,
307    ) -> Self {
308        Self {
309            continuation: Some(continuation),
310            settlements,
311        }
312    }
313
314    /// Check if there are more results
315    pub fn has_more(&self) -> bool {
316        self.continuation.is_some()
317    }
318}
319
320/// Contract size response
321#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
322pub struct ContractSizeResponse {
323    /// Contract size value
324    pub contract_size: f64,
325}
326
327/// Status response
328#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
329pub struct StatusResponse {
330    /// Whether the system is locked (optional)
331    pub locked: Option<bool>,
332    /// Status message (optional)
333    pub message: Option<String>,
334    /// List of locked indices (optional)
335    pub locked_indices: Option<Vec<String>>,
336    /// Additional fields that might be present in the API response
337    #[serde(flatten)]
338    pub additional_fields: std::collections::HashMap<String, serde_json::Value>,
339}
340
341/// APR history response
342#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
343pub struct AprHistoryResponse {
344    /// List of APR data points
345    pub data: Vec<AprDataPoint>,
346    /// Continuation token for pagination
347    pub continuation: Option<String>,
348}
349/// APR data point
350#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
351pub struct AprDataPoint {
352    /// Annual percentage rate
353    pub apr: f64,
354    /// Timestamp of the data point (optional)
355    pub timestamp: Option<u64>,
356    /// Day of the data point
357    pub day: i32,
358}
359
360/// Hello response
361#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
362pub struct HelloResponse {
363    /// Version string
364    pub version: String,
365}
366
367/// Delivery prices response
368#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
369pub struct DeliveryPricesResponse {
370    /// List of delivery price data
371    pub data: Vec<DeliveryPriceData>,
372    /// Total number of records
373    pub records_total: u32,
374}
375
376/// Delivery price data
377#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
378pub struct DeliveryPriceData {
379    /// Date of the delivery price
380    pub date: String,
381    /// Delivery price value
382    pub delivery_price: f64,
383}
384
385/// Currency-specific expirations
386#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
387pub struct CurrencyExpirations {
388    /// Future instrument expirations
389    pub future: Option<Vec<String>>,
390    /// Option instrument expirations
391    pub option: Option<Vec<String>>,
392}
393
394/// Expirations response
395#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
396pub struct ExpirationsResponse {
397    /// Direct future expirations (when currency="any")
398    pub future: Option<Vec<String>>,
399    /// Direct option expirations (when currency="any")
400    pub option: Option<Vec<String>>,
401    /// Map of currency to their expirations (when specific currency)
402    #[serde(flatten)]
403    pub currencies: std::collections::HashMap<String, CurrencyExpirations>,
404}
405
406/// Last trades response
407#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
408pub struct LastTradesResponse {
409    /// Whether there are more trades available
410    pub has_more: bool,
411    /// List of recent trades
412    pub trades: Vec<LastTrade>,
413}
414
415/// Order response
416#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
417pub struct OrderResponse {
418    /// Order information
419    pub order: OrderInfo,
420    /// List of trade executions for the order
421    pub trades: Vec<TradeExecution>,
422}
423
424/// Mass quote response
425#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
426pub struct MassQuoteResponse {
427    /// List of quote results
428    pub quotes: Vec<QuoteResult>,
429}
430
431impl std::error::Error for JsonRpcError {}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436    use serde_json::json;
437
438    #[test]
439    fn test_json_rpc_response_success() {
440        let response = JsonRpcResponse::success(Some(json!(1)), "test_result".to_string());
441        assert_eq!(response.jsonrpc, "2.0");
442        assert_eq!(response.id, Some(json!(1)));
443        assert_eq!(response.result, Some("test_result".to_string()));
444        assert_eq!(response.error, None);
445        assert!(response.is_success());
446        assert!(!response.is_error());
447    }
448
449    #[test]
450    fn test_json_rpc_response_error() {
451        let error = JsonRpcError::new(-32600, "Invalid Request".to_string());
452        let response: JsonRpcResponse<String> =
453            JsonRpcResponse::error(Some(json!(1)), error.clone());
454
455        assert_eq!(response.jsonrpc, "2.0");
456        assert_eq!(response.id, Some(json!(1)));
457        assert_eq!(response.result, None);
458        assert!(response.error.is_some());
459        assert!(!response.is_success());
460        assert!(response.is_error());
461    }
462
463    #[test]
464    fn test_json_rpc_response_into_result_success() {
465        let response = JsonRpcResponse::success(Some(json!(1)), "test_result".to_string());
466        let result = response.into_result().unwrap();
467        assert_eq!(result, "test_result");
468    }
469
470    #[test]
471    fn test_json_rpc_response_into_result_error() {
472        let error = JsonRpcError::new(-32600, "Invalid Request".to_string());
473        let response: JsonRpcResponse<String> =
474            JsonRpcResponse::error(Some(json!(1)), error.clone());
475        let result = response.into_result();
476        assert!(result.is_err());
477        let err = result.unwrap_err();
478        assert_eq!(err.code, -32600);
479        assert_eq!(err.message, "Invalid Request");
480    }
481
482    #[test]
483    fn test_json_rpc_response_into_result_neither() {
484        let response: JsonRpcResponse<String> = JsonRpcResponse {
485            jsonrpc: "2.0".to_string(),
486            id: Some(json!(1)),
487            result: None,
488            error: None,
489            testnet: None,
490            us_in: None,
491            us_out: None,
492            us_diff: None,
493        };
494        let result = response.into_result();
495        assert!(result.is_err());
496        let err = result.unwrap_err();
497        assert_eq!(err.code, -32603);
498        assert!(err.message.contains("Internal error"));
499    }
500
501    #[test]
502    fn test_json_rpc_error_new() {
503        let error = JsonRpcError::new(-32600, "Invalid Request".to_string());
504        assert_eq!(error.code, -32600);
505        assert_eq!(error.message, "Invalid Request");
506        assert_eq!(error.data, None);
507    }
508
509    #[test]
510    fn test_json_rpc_error_with_data() {
511        let data = json!({"details": "Additional error information"});
512        let error = JsonRpcError::with_data(-32602, "Invalid params".to_string(), data.clone());
513        assert_eq!(error.code, -32602);
514        assert_eq!(error.message, "Invalid params");
515        assert_eq!(error.data, Some(data));
516    }
517
518    #[test]
519    fn test_json_rpc_error_standard_errors() {
520        let parse_error = JsonRpcError::parse_error();
521        assert_eq!(parse_error.code, -32700);
522        assert_eq!(parse_error.message, "Parse error");
523
524        let invalid_request = JsonRpcError::invalid_request();
525        assert_eq!(invalid_request.code, -32600);
526        assert_eq!(invalid_request.message, "Invalid Request");
527
528        let method_not_found = JsonRpcError::method_not_found();
529        assert_eq!(method_not_found.code, -32601);
530        assert_eq!(method_not_found.message, "Method not found");
531
532        let invalid_params = JsonRpcError::invalid_params();
533        assert_eq!(invalid_params.code, -32602);
534        assert_eq!(invalid_params.message, "Invalid params");
535
536        let internal_error = JsonRpcError::internal_error();
537        assert_eq!(internal_error.code, -32603);
538        assert_eq!(internal_error.message, "Internal error");
539    }
540
541    #[test]
542    fn test_json_rpc_error_is_server_error() {
543        let server_error = JsonRpcError::new(-32001, "Server error".to_string());
544        assert!(server_error.is_server_error());
545        assert!(!server_error.is_application_error());
546
547        let app_error = JsonRpcError::new(-31999, "Application error".to_string());
548        assert!(!app_error.is_server_error());
549        assert!(app_error.is_application_error());
550    }
551
552    #[test]
553    fn test_json_rpc_error_display() {
554        let error = JsonRpcError::new(-32600, "Invalid Request".to_string());
555        let display_str = format!("{}", error);
556        // The display implementation uses JSON formatting from the macro
557        assert!(display_str.contains("-32600"));
558        assert!(display_str.contains("Invalid Request"));
559    }
560
561    #[test]
562    fn test_auth_response() {
563        let auth_response = AuthResponse {
564            access_token: "access_token_123".to_string(),
565            token_type: "bearer".to_string(),
566            expires_in: 3600,
567            refresh_token: "refresh_token_456".to_string(),
568            scope: "read write".to_string(),
569        };
570
571        assert_eq!(auth_response.access_token, "access_token_123");
572        assert_eq!(auth_response.token_type, "bearer");
573        assert_eq!(auth_response.expires_in, 3600);
574        assert_eq!(auth_response.refresh_token, "refresh_token_456");
575        assert_eq!(auth_response.scope, "read write");
576    }
577
578    #[test]
579    fn test_pagination() {
580        let pagination = Pagination {
581            page: Some(1),
582            per_page: Some(50),
583            total: Some(1000),
584            pages: Some(20),
585            has_more: Some(true),
586        };
587
588        assert_eq!(pagination.page, Some(1));
589        assert_eq!(pagination.per_page, Some(50));
590        assert_eq!(pagination.total, Some(1000));
591        assert_eq!(pagination.pages, Some(20));
592        assert_eq!(pagination.has_more, Some(true));
593    }
594
595    #[test]
596    fn test_paginated_response_new() {
597        let data = vec!["item1".to_string(), "item2".to_string()];
598        let response = PaginatedResponse::new(data.clone());
599
600        assert_eq!(response.data, data);
601        assert_eq!(response.pagination, None);
602        assert_eq!(response.len(), 2);
603        assert!(!response.is_empty());
604        assert!(!response.has_more());
605    }
606
607    #[test]
608    fn test_paginated_response_with_pagination() {
609        let data = vec!["item1".to_string(), "item2".to_string()];
610        let pagination = Pagination {
611            page: Some(1),
612            per_page: Some(2),
613            total: Some(10),
614            pages: Some(5),
615            has_more: Some(true),
616        };
617        let response = PaginatedResponse::with_pagination(data.clone(), pagination);
618
619        assert_eq!(response.data, data);
620        assert!(response.pagination.is_some());
621        assert_eq!(response.len(), 2);
622        assert!(!response.is_empty());
623        assert!(response.has_more());
624    }
625
626    #[test]
627    fn test_paginated_response_empty() {
628        let response: PaginatedResponse<String> = PaginatedResponse::new(vec![]);
629        assert_eq!(response.len(), 0);
630        assert!(response.is_empty());
631        assert!(!response.has_more());
632    }
633
634    #[test]
635    fn test_notification() {
636        let notification = Notification::new("ticker".to_string(), "BTC-PERPETUAL".to_string());
637        assert_eq!(notification.jsonrpc, "2.0");
638        assert_eq!(notification.method, "ticker");
639        assert_eq!(notification.params, "BTC-PERPETUAL");
640    }
641
642    #[test]
643    fn test_subscription_response() {
644        let subscription = SubscriptionResponse {
645            subscription: "sub_123".to_string(),
646            channel: "ticker.BTC-PERPETUAL".to_string(),
647        };
648        assert_eq!(subscription.subscription, "sub_123");
649        assert_eq!(subscription.channel, "ticker.BTC-PERPETUAL");
650    }
651
652    #[test]
653    fn test_heartbeat_response() {
654        let heartbeat = HeartbeatResponse {
655            type_: "heartbeat".to_string(),
656        };
657        assert_eq!(heartbeat.type_, "heartbeat");
658    }
659
660    #[test]
661    fn test_test_response() {
662        let test_response = TestResponse {
663            version: "1.2.3".to_string(),
664        };
665        assert_eq!(test_response.version, "1.2.3");
666    }
667
668    #[test]
669    fn test_server_time_response() {
670        let server_time = ServerTimeResponse {
671            timestamp: 1640995200000,
672        };
673        assert_eq!(server_time.timestamp, 1640995200000);
674    }
675
676    #[test]
677    fn test_settlements_response_new() {
678        let settlements = vec![];
679        let response = SettlementsResponse::new(settlements);
680        assert_eq!(response.continuation, None);
681        assert!(response.settlements.is_empty());
682        assert!(!response.has_more());
683    }
684
685    #[test]
686    fn test_settlements_response_with_continuation() {
687        let settlements = vec![];
688        let response = SettlementsResponse::with_continuation(settlements, "token_123".to_string());
689        assert_eq!(response.continuation, Some("token_123".to_string()));
690        assert!(response.has_more());
691    }
692
693    #[test]
694    fn test_contract_size_response() {
695        let contract_size = ContractSizeResponse { contract_size: 1.0 };
696        assert_eq!(contract_size.contract_size, 1.0);
697    }
698
699    #[test]
700    fn test_status_response() {
701        let mut additional_fields = std::collections::HashMap::new();
702        additional_fields.insert("custom_field".to_string(), json!("custom_value"));
703
704        let status = StatusResponse {
705            locked: Some(false),
706            message: Some("System operational".to_string()),
707            locked_indices: Some(vec!["BTC".to_string(), "ETH".to_string()]),
708            additional_fields,
709        };
710
711        assert_eq!(status.locked, Some(false));
712        assert_eq!(status.message, Some("System operational".to_string()));
713        assert_eq!(
714            status.locked_indices,
715            Some(vec!["BTC".to_string(), "ETH".to_string()])
716        );
717        assert_eq!(
718            status.additional_fields.get("custom_field"),
719            Some(&json!("custom_value"))
720        );
721    }
722
723    #[test]
724    fn test_apr_data_point() {
725        let apr_point = AprDataPoint {
726            apr: 5.25,
727            timestamp: Some(1640995200000),
728            day: 365,
729        };
730        assert_eq!(apr_point.apr, 5.25);
731        assert_eq!(apr_point.timestamp, Some(1640995200000));
732        assert_eq!(apr_point.day, 365);
733    }
734
735    #[test]
736    fn test_apr_history_response() {
737        let data_points = vec![AprDataPoint {
738            apr: 5.25,
739            timestamp: Some(1640995200000),
740            day: 365,
741        }];
742        let apr_history = AprHistoryResponse {
743            data: data_points,
744            continuation: Some("token_456".to_string()),
745        };
746        assert_eq!(apr_history.data.len(), 1);
747        assert_eq!(apr_history.continuation, Some("token_456".to_string()));
748    }
749
750    #[test]
751    fn test_hello_response() {
752        let hello = HelloResponse {
753            version: "2.1.1".to_string(),
754        };
755        assert_eq!(hello.version, "2.1.1");
756    }
757
758    #[test]
759    fn test_delivery_price_data() {
760        let delivery_price = DeliveryPriceData {
761            date: "2024-01-01".to_string(),
762            delivery_price: 50000.0,
763        };
764        assert_eq!(delivery_price.date, "2024-01-01");
765        assert_eq!(delivery_price.delivery_price, 50000.0);
766    }
767
768    #[test]
769    fn test_delivery_prices_response() {
770        let data = vec![DeliveryPriceData {
771            date: "2024-01-01".to_string(),
772            delivery_price: 50000.0,
773        }];
774        let delivery_prices = DeliveryPricesResponse {
775            data,
776            records_total: 1,
777        };
778        assert_eq!(delivery_prices.data.len(), 1);
779        assert_eq!(delivery_prices.records_total, 1);
780    }
781
782    #[test]
783    fn test_currency_expirations() {
784        let expirations = CurrencyExpirations {
785            future: Some(vec!["2024-03-29".to_string()]),
786            option: Some(vec!["2024-01-26".to_string(), "2024-02-23".to_string()]),
787        };
788        assert_eq!(expirations.future, Some(vec!["2024-03-29".to_string()]));
789        assert_eq!(
790            expirations.option,
791            Some(vec!["2024-01-26".to_string(), "2024-02-23".to_string()])
792        );
793    }
794
795    #[test]
796    fn test_expirations_response() {
797        let mut currencies = std::collections::HashMap::new();
798        currencies.insert(
799            "BTC".to_string(),
800            CurrencyExpirations {
801                future: Some(vec!["2024-03-29".to_string()]),
802                option: Some(vec!["2024-01-26".to_string()]),
803            },
804        );
805
806        let expirations = ExpirationsResponse {
807            future: Some(vec!["2024-03-29".to_string()]),
808            option: Some(vec!["2024-01-26".to_string()]),
809            currencies,
810        };
811
812        assert_eq!(expirations.future, Some(vec!["2024-03-29".to_string()]));
813        assert_eq!(expirations.option, Some(vec!["2024-01-26".to_string()]));
814        assert!(expirations.currencies.contains_key("BTC"));
815    }
816
817    #[test]
818    fn test_last_trades_response() {
819        let last_trades = LastTradesResponse {
820            has_more: true,
821            trades: vec![],
822        };
823        assert!(last_trades.has_more);
824        assert!(last_trades.trades.is_empty());
825    }
826
827    #[test]
828    fn test_serialization_roundtrip() {
829        let response = JsonRpcResponse::success(Some(json!(1)), "test_result".to_string());
830        let json = serde_json::to_string(&response).unwrap();
831        let deserialized: JsonRpcResponse<String> = serde_json::from_str(&json).unwrap();
832
833        assert_eq!(response.jsonrpc, deserialized.jsonrpc);
834        assert_eq!(response.id, deserialized.id);
835        assert_eq!(response.result, deserialized.result);
836        assert_eq!(response.error.is_none(), deserialized.error.is_none());
837    }
838
839    #[test]
840    fn test_debug_and_display_implementations() {
841        let auth_response = AuthResponse {
842            access_token: "token".to_string(),
843            token_type: "bearer".to_string(),
844            expires_in: 3600,
845            refresh_token: "refresh".to_string(),
846            scope: "read".to_string(),
847        };
848
849        let debug_str = format!("{:?}", auth_response);
850        let display_str = format!("{}", auth_response);
851
852        assert!(debug_str.contains("token"));
853        assert!(display_str.contains("token"));
854    }
855}