barter_data/exchange/coinbase/
subscription.rs1use barter_integration::{Validator, error::SocketError};
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
27#[serde(tag = "type", rename_all = "lowercase")]
28pub enum CoinbaseSubResponse {
29 #[serde(alias = "subscriptions")]
30 Subscribed {
31 channels: Vec<CoinbaseChannels>,
32 },
33 Error {
34 reason: String,
35 },
36}
37
38#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
45pub struct CoinbaseChannels {
46 #[serde(alias = "name")]
47 pub channel: String,
48 pub product_ids: Vec<String>,
49}
50
51impl Validator for CoinbaseSubResponse {
52 type Error = SocketError;
53
54 fn validate(self) -> Result<Self, SocketError>
55 where
56 Self: Sized,
57 {
58 match &self {
59 CoinbaseSubResponse::Subscribed { .. } => Ok(self),
60 CoinbaseSubResponse::Error { reason } => Err(SocketError::Subscribe(format!(
61 "received failure subscription response: {reason}",
62 ))),
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 mod de {
72 use super::*;
73
74 #[test]
75 fn test_coinbase_sub_response() {
76 struct TestCase {
77 input: &'static str,
78 expected: Result<CoinbaseSubResponse, SocketError>,
79 }
80
81 let cases = vec![
82 TestCase {
83 input: r#"
85 {
86 "type":"subscriptions",
87 "channels":[
88 {"name":"matches","product_ids":["BTC-USD", "ETH-USD"]}
89 ]
90 }
91 "#,
92 expected: Ok(CoinbaseSubResponse::Subscribed {
93 channels: vec![CoinbaseChannels {
94 channel: "matches".to_string(),
95 product_ids: vec!["BTC-USD".to_string(), "ETH-USD".to_string()],
96 }],
97 }),
98 },
99 TestCase {
100 input: r#"
102 {
103 "type":"error",
104 "message":"Failed to subscribe",
105 "reason":"GIBBERISH-USD is not a valid product"
106 }
107 "#,
108 expected: Ok(CoinbaseSubResponse::Error {
109 reason: "GIBBERISH-USD is not a valid product".to_string(),
110 }),
111 },
112 ];
113
114 for (index, test) in cases.into_iter().enumerate() {
115 let actual = serde_json::from_str::<CoinbaseSubResponse>(test.input);
116 match (actual, test.expected) {
117 (Ok(actual), Ok(expected)) => {
118 assert_eq!(actual, expected, "TC{} failed", index)
119 }
120 (Err(_), Err(_)) => {
121 }
123 (actual, expected) => {
124 panic!(
126 "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
127 );
128 }
129 }
130 }
131 }
132 }
133
134 #[test]
135 fn test_validate_coinbase_sub_response() {
136 struct TestCase {
137 input_response: CoinbaseSubResponse,
138 is_valid: bool,
139 }
140
141 let cases = vec![
142 TestCase {
143 input_response: CoinbaseSubResponse::Subscribed {
145 channels: vec![CoinbaseChannels {
146 channel: "matches".to_string(),
147 product_ids: vec!["BTC-USD".to_string(), "ETH-USD".to_string()],
148 }],
149 },
150 is_valid: true,
151 },
152 TestCase {
153 input_response: CoinbaseSubResponse::Error {
155 reason: "GIBBERISH-USD is not a valid product".to_string(),
156 },
157 is_valid: false,
158 },
159 ];
160
161 for (index, test) in cases.into_iter().enumerate() {
162 let actual = test.input_response.validate().is_ok();
163 assert_eq!(actual, test.is_valid, "TestCase {} failed", index);
164 }
165 }
166}