use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ClobAuth {
pub key: String,
pub secret: String,
pub passphrase: String,
}
impl ClobAuth {
pub fn new(
key: impl Into<String>,
secret: impl Into<String>,
passphrase: impl Into<String>,
) -> Self {
Self {
key: key.into(),
secret: secret.into(),
passphrase: passphrase.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GammaAuth {
pub address: String,
}
impl GammaAuth {
pub fn new(address: impl Into<String>) -> Self {
Self {
address: address.into(),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Action {
Subscribe,
Unsubscribe,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Subscription {
pub topic: String,
#[serde(rename = "type")]
pub message_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub filters: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub clob_auth: Option<ClobAuth>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gamma_auth: Option<GammaAuth>,
}
impl Subscription {
pub fn new(topic: impl Into<String>, message_type: impl Into<String>) -> Self {
Self {
topic: topic.into(),
message_type: message_type.into(),
filters: None,
clob_auth: None,
gamma_auth: None,
}
}
pub fn all(topic: impl Into<String>) -> Self {
Self::new(topic, "*")
}
pub fn with_filter(mut self, filter: impl Into<String>) -> Self {
self.filters = Some(filter.into());
self
}
pub fn with_clob_auth(mut self, auth: ClobAuth) -> Self {
self.clob_auth = Some(auth);
self
}
pub fn with_gamma_auth(mut self, auth: GammaAuth) -> Self {
self.gamma_auth = Some(auth);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscriptionRequest {
pub action: Action,
pub subscriptions: Vec<Subscription>,
}
impl SubscriptionRequest {
pub fn subscribe(subscriptions: Vec<Subscription>) -> Self {
Self {
action: Action::Subscribe,
subscriptions,
}
}
pub fn unsubscribe(subscriptions: Vec<Subscription>) -> Self {
Self {
action: Action::Unsubscribe,
subscriptions,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionStatus {
Connecting,
Connected,
Disconnected,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub topic: String,
#[serde(rename = "type")]
pub message_type: String,
pub timestamp: u64,
pub payload: serde_json::Value,
#[serde(default)]
pub connection_id: String,
}
impl Message {
pub fn parse_payload<T: for<'de> Deserialize<'de>>(&self) -> Result<T, serde_json::Error> {
serde_json::from_value(self.payload.clone())
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum MessagePayload {
Object(serde_json::Value),
Array(Vec<serde_json::Value>),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subscription_serialization() {
let sub = Subscription::new("activity", "trades").with_filter(r#"{"event_slug":"test"}"#);
let json = serde_json::to_string(&sub).unwrap();
assert!(json.contains(r#""topic":"activity""#));
assert!(json.contains(r#""type":"trades""#));
assert!(json.contains(r#""filters":"{\"event_slug\":\"test\"}""#));
}
#[test]
fn test_subscription_request_serialization() {
let req =
SubscriptionRequest::subscribe(vec![Subscription::new("crypto_prices", "update")]);
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains(r#""action":"subscribe""#));
}
#[test]
fn test_clob_auth_serialization() {
let sub = Subscription::new("clob_user", "*")
.with_clob_auth(ClobAuth::new("key", "secret", "pass"));
let json = serde_json::to_string(&sub).unwrap();
assert!(json.contains(r#""clob_auth""#));
assert!(json.contains(r#""key":"key""#));
}
#[test]
fn test_message_deserialization() {
let json = r#"{
"topic": "crypto_prices",
"type": "update",
"timestamp": 1234567890,
"payload": {"symbol": "BTCUSDT", "value": "50000"},
"connection_id": "conn-123"
}"#;
let msg: Message = serde_json::from_str(json).unwrap();
assert_eq!(msg.topic, "crypto_prices");
assert_eq!(msg.message_type, "update");
assert_eq!(msg.timestamp, 1234567890);
assert_eq!(msg.connection_id, "conn-123");
}
#[test]
fn test_message_parse_payload() {
#[derive(Debug, Deserialize, PartialEq)]
struct TestPayload {
symbol: String,
value: String,
}
let json = r#"{
"topic": "test",
"type": "test",
"timestamp": 0,
"payload": {"symbol": "BTC", "value": "100"}
}"#;
let msg: Message = serde_json::from_str(json).unwrap();
let payload: TestPayload = msg.parse_payload().unwrap();
assert_eq!(payload.symbol, "BTC");
assert_eq!(payload.value, "100");
}
}