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 fn validate(self) -> Result<Self, SocketError>
53 where
54 Self: Sized,
55 {
56 match &self {
57 CoinbaseSubResponse::Subscribed { .. } => Ok(self),
58 CoinbaseSubResponse::Error { reason } => Err(SocketError::Subscribe(format!(
59 "received failure subscription response: {}",
60 reason
61 ))),
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 mod de {
71 use super::*;
72
73 #[test]
74 fn test_coinbase_sub_response() {
75 struct TestCase {
76 input: &'static str,
77 expected: Result<CoinbaseSubResponse, SocketError>,
78 }
79
80 let cases = vec![
81 TestCase {
82 input: r#"
84 {
85 "type":"subscriptions",
86 "channels":[
87 {"name":"matches","product_ids":["BTC-USD", "ETH-USD"]}
88 ]
89 }
90 "#,
91 expected: Ok(CoinbaseSubResponse::Subscribed {
92 channels: vec![CoinbaseChannels {
93 channel: "matches".to_string(),
94 product_ids: vec!["BTC-USD".to_string(), "ETH-USD".to_string()],
95 }],
96 }),
97 },
98 TestCase {
99 input: r#"
101 {
102 "type":"error",
103 "message":"Failed to subscribe",
104 "reason":"GIBBERISH-USD is not a valid product"
105 }
106 "#,
107 expected: Ok(CoinbaseSubResponse::Error {
108 reason: "GIBBERISH-USD is not a valid product".to_string(),
109 }),
110 },
111 ];
112
113 for (index, test) in cases.into_iter().enumerate() {
114 let actual = serde_json::from_str::<CoinbaseSubResponse>(test.input);
115 match (actual, test.expected) {
116 (Ok(actual), Ok(expected)) => {
117 assert_eq!(actual, expected, "TC{} failed", index)
118 }
119 (Err(_), Err(_)) => {
120 }
122 (actual, expected) => {
123 panic!(
125 "TC{index} failed because actual != expected. \nActual: {actual:?}\nExpected: {expected:?}\n"
126 );
127 }
128 }
129 }
130 }
131 }
132
133 #[test]
134 fn test_validate_coinbase_sub_response() {
135 struct TestCase {
136 input_response: CoinbaseSubResponse,
137 is_valid: bool,
138 }
139
140 let cases = vec![
141 TestCase {
142 input_response: CoinbaseSubResponse::Subscribed {
144 channels: vec![CoinbaseChannels {
145 channel: "matches".to_string(),
146 product_ids: vec!["BTC-USD".to_string(), "ETH-USD".to_string()],
147 }],
148 },
149 is_valid: true,
150 },
151 TestCase {
152 input_response: CoinbaseSubResponse::Error {
154 reason: "GIBBERISH-USD is not a valid product".to_string(),
155 },
156 is_valid: false,
157 },
158 ];
159
160 for (index, test) in cases.into_iter().enumerate() {
161 let actual = test.input_response.validate().is_ok();
162 assert_eq!(actual, test.is_valid, "TestCase {} failed", index);
163 }
164 }
165}