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