1use std::{
2 cmp::Ordering,
3 fmt::Display,
4 ops::{Deref, DerefMut},
5};
6
7use derive_more::derive::From;
8use itertools::Itertools as _;
9use serde::{de::Error, Deserialize, Serialize};
10
11use crate::{
12 payload::AggregatedPriceFeedData,
13 time::{DurationUs, FixedRate, TimestampUs},
14 ChannelId, Price, PriceFeedId, PriceFeedProperty, Rate,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct LatestPriceRequestRepr {
20 pub price_feed_ids: Option<Vec<PriceFeedId>>,
22 pub symbols: Option<Vec<String>>,
23 pub properties: Vec<PriceFeedProperty>,
24 #[serde(alias = "chains")]
26 pub formats: Vec<Format>,
27 #[serde(default)]
28 pub json_binary_encoding: JsonBinaryEncoding,
29 #[serde(default = "default_parsed")]
32 pub parsed: bool,
33 pub channel: Channel,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub struct LatestPriceRequest(LatestPriceRequestRepr);
39
40impl<'de> Deserialize<'de> for LatestPriceRequest {
41 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
42 where
43 D: serde::Deserializer<'de>,
44 {
45 let value = LatestPriceRequestRepr::deserialize(deserializer)?;
46 Self::new(value).map_err(Error::custom)
47 }
48}
49
50impl LatestPriceRequest {
51 pub fn new(value: LatestPriceRequestRepr) -> Result<Self, &'static str> {
52 validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
53 validate_optional_nonempty_vec_has_unique_elements(
54 &value.price_feed_ids,
55 "no price feed ids specified",
56 "duplicate price feed ids specified",
57 )?;
58 validate_optional_nonempty_vec_has_unique_elements(
59 &value.symbols,
60 "no symbols specified",
61 "duplicate symbols specified",
62 )?;
63 validate_formats(&value.formats)?;
64 validate_properties(&value.properties)?;
65 Ok(Self(value))
66 }
67}
68
69impl Deref for LatestPriceRequest {
70 type Target = LatestPriceRequestRepr;
71
72 fn deref(&self) -> &Self::Target {
73 &self.0
74 }
75}
76impl DerefMut for LatestPriceRequest {
77 fn deref_mut(&mut self) -> &mut Self::Target {
78 &mut self.0
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct PriceRequestRepr {
85 pub timestamp: TimestampUs,
86 pub price_feed_ids: Option<Vec<PriceFeedId>>,
88 pub symbols: Option<Vec<String>>,
89 pub properties: Vec<PriceFeedProperty>,
90 pub formats: Vec<Format>,
91 #[serde(default)]
92 pub json_binary_encoding: JsonBinaryEncoding,
93 #[serde(default = "default_parsed")]
96 pub parsed: bool,
97 pub channel: Channel,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
101#[serde(rename_all = "camelCase")]
102pub struct PriceRequest(PriceRequestRepr);
103
104impl<'de> Deserialize<'de> for PriceRequest {
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: serde::Deserializer<'de>,
108 {
109 let value = PriceRequestRepr::deserialize(deserializer)?;
110 Self::new(value).map_err(Error::custom)
111 }
112}
113
114impl PriceRequest {
115 pub fn new(value: PriceRequestRepr) -> Result<Self, &'static str> {
116 validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
117 validate_optional_nonempty_vec_has_unique_elements(
118 &value.price_feed_ids,
119 "no price feed ids specified",
120 "duplicate price feed ids specified",
121 )?;
122 validate_optional_nonempty_vec_has_unique_elements(
123 &value.symbols,
124 "no symbols specified",
125 "duplicate symbols specified",
126 )?;
127 validate_formats(&value.formats)?;
128 validate_properties(&value.properties)?;
129 Ok(Self(value))
130 }
131}
132
133impl Deref for PriceRequest {
134 type Target = PriceRequestRepr;
135
136 fn deref(&self) -> &Self::Target {
137 &self.0
138 }
139}
140impl DerefMut for PriceRequest {
141 fn deref_mut(&mut self) -> &mut Self::Target {
142 &mut self.0
143 }
144}
145
146#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct ReducePriceRequest {
149 pub payload: JsonUpdate,
150 pub price_feed_ids: Vec<PriceFeedId>,
151}
152
153pub type LatestPriceResponse = JsonUpdate;
154pub type ReducePriceResponse = JsonUpdate;
155pub type PriceResponse = JsonUpdate;
156
157pub fn default_parsed() -> bool {
158 true
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub enum DeliveryFormat {
164 #[default]
166 Json,
167 Binary,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub enum Format {
174 Evm,
175 Solana,
176 LeEcdsa,
177 LeUnsigned,
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub enum JsonBinaryEncoding {
183 #[default]
184 Base64,
185 Hex,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, From)]
189pub enum Channel {
190 FixedRate(FixedRate),
191 RealTime,
192}
193
194impl PartialOrd for Channel {
195 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
196 let rate_left = match self {
197 Channel::FixedRate(rate) => rate.duration().as_micros(),
198 Channel::RealTime => FixedRate::MIN.duration().as_micros(),
199 };
200 let rate_right = match other {
201 Channel::FixedRate(rate) => rate.duration().as_micros(),
202 Channel::RealTime => FixedRate::MIN.duration().as_micros(),
203 };
204 Some(rate_left.cmp(&rate_right))
205 }
206}
207
208impl Serialize for Channel {
209 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
210 where
211 S: serde::Serializer,
212 {
213 match self {
214 Channel::FixedRate(fixed_rate) => serializer.serialize_str(&format!(
215 "fixed_rate@{}ms",
216 fixed_rate.duration().as_millis()
217 )),
218 Channel::RealTime => serializer.serialize_str("real_time"),
219 }
220 }
221}
222
223impl Display for Channel {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 match self {
226 Channel::FixedRate(fixed_rate) => {
227 write!(f, "fixed_rate@{}ms", fixed_rate.duration().as_millis())
228 }
229 Channel::RealTime => write!(f, "real_time"),
230 }
231 }
232}
233
234impl Channel {
235 pub fn id(&self) -> ChannelId {
236 match self {
237 Channel::FixedRate(fixed_rate) => match fixed_rate.duration().as_millis() {
238 50 => ChannelId::FIXED_RATE_50,
239 200 => ChannelId::FIXED_RATE_200,
240 1000 => ChannelId::FIXED_RATE_1000,
241 _ => panic!("unknown channel: {self:?}"),
242 },
243 Channel::RealTime => ChannelId::REAL_TIME,
244 }
245 }
246}
247
248#[test]
249fn id_supports_all_fixed_rates() {
250 for rate in FixedRate::ALL {
251 Channel::FixedRate(rate).id();
252 }
253}
254
255fn parse_channel(value: &str) -> Option<Channel> {
256 if value == "real_time" {
257 Some(Channel::RealTime)
258 } else if let Some(rest) = value.strip_prefix("fixed_rate@") {
259 let ms_value = rest.strip_suffix("ms")?;
260 Some(Channel::FixedRate(FixedRate::from_millis(
261 ms_value.parse().ok()?,
262 )?))
263 } else {
264 None
265 }
266}
267
268impl<'de> Deserialize<'de> for Channel {
269 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
270 where
271 D: serde::Deserializer<'de>,
272 {
273 let value = <String>::deserialize(deserializer)?;
274 parse_channel(&value).ok_or_else(|| Error::custom("unknown channel"))
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct SubscriptionParamsRepr {
281 pub price_feed_ids: Option<Vec<PriceFeedId>>,
283 pub symbols: Option<Vec<String>>,
284 pub properties: Vec<PriceFeedProperty>,
285 #[serde(alias = "chains")]
287 pub formats: Vec<Format>,
288 #[serde(default)]
289 pub delivery_format: DeliveryFormat,
290 #[serde(default)]
291 pub json_binary_encoding: JsonBinaryEncoding,
292 #[serde(default = "default_parsed")]
295 pub parsed: bool,
296 pub channel: Channel,
297 #[serde(default, alias = "ignoreInvalidFeedIds")]
299 pub ignore_invalid_feeds: bool,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
303#[serde(rename_all = "camelCase")]
304pub struct SubscriptionParams(SubscriptionParamsRepr);
305
306impl<'de> Deserialize<'de> for SubscriptionParams {
307 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
308 where
309 D: serde::Deserializer<'de>,
310 {
311 let value = SubscriptionParamsRepr::deserialize(deserializer)?;
312 Self::new(value).map_err(Error::custom)
313 }
314}
315
316impl SubscriptionParams {
317 pub fn new(value: SubscriptionParamsRepr) -> Result<Self, &'static str> {
318 validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
319 validate_optional_nonempty_vec_has_unique_elements(
320 &value.price_feed_ids,
321 "no price feed ids specified",
322 "duplicate price feed ids specified",
323 )?;
324 validate_optional_nonempty_vec_has_unique_elements(
325 &value.symbols,
326 "no symbols specified",
327 "duplicate symbols specified",
328 )?;
329 validate_formats(&value.formats)?;
330 validate_properties(&value.properties)?;
331 Ok(Self(value))
332 }
333}
334
335impl Deref for SubscriptionParams {
336 type Target = SubscriptionParamsRepr;
337
338 fn deref(&self) -> &Self::Target {
339 &self.0
340 }
341}
342impl DerefMut for SubscriptionParams {
343 fn deref_mut(&mut self) -> &mut Self::Target {
344 &mut self.0
345 }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
349#[serde(rename_all = "camelCase")]
350pub struct JsonBinaryData {
351 pub encoding: JsonBinaryEncoding,
352 pub data: String,
353}
354
355#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
356#[serde(rename_all = "camelCase")]
357pub struct JsonUpdate {
358 #[serde(skip_serializing_if = "Option::is_none")]
360 pub parsed: Option<ParsedPayload>,
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub evm: Option<JsonBinaryData>,
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub solana: Option<JsonBinaryData>,
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub le_ecdsa: Option<JsonBinaryData>,
370 #[serde(skip_serializing_if = "Option::is_none")]
372 pub le_unsigned: Option<JsonBinaryData>,
373}
374
375#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct ParsedPayload {
378 #[serde(with = "crate::serde_str::timestamp")]
379 pub timestamp_us: TimestampUs,
380 pub price_feeds: Vec<ParsedFeedPayload>,
381}
382
383#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct ParsedFeedPayload {
386 pub price_feed_id: PriceFeedId,
387 #[serde(skip_serializing_if = "Option::is_none")]
388 #[serde(with = "crate::serde_str::option_price")]
389 #[serde(default)]
390 pub price: Option<Price>,
391 #[serde(skip_serializing_if = "Option::is_none")]
392 #[serde(with = "crate::serde_str::option_price")]
393 #[serde(default)]
394 pub best_bid_price: Option<Price>,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 #[serde(with = "crate::serde_str::option_price")]
397 #[serde(default)]
398 pub best_ask_price: Option<Price>,
399 #[serde(skip_serializing_if = "Option::is_none")]
400 #[serde(default)]
401 pub publisher_count: Option<u16>,
402 #[serde(skip_serializing_if = "Option::is_none")]
403 #[serde(default)]
404 pub exponent: Option<i16>,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 #[serde(default)]
407 pub confidence: Option<Price>,
408 #[serde(skip_serializing_if = "Option::is_none")]
409 #[serde(default)]
410 pub funding_rate: Option<Rate>,
411 #[serde(skip_serializing_if = "Option::is_none")]
412 #[serde(default)]
413 pub funding_timestamp: Option<TimestampUs>,
414 #[serde(skip_serializing_if = "Option::is_none")]
416 #[serde(default)]
417 pub funding_rate_interval: Option<DurationUs>,
418}
419
420impl ParsedFeedPayload {
421 pub fn new(
422 price_feed_id: PriceFeedId,
423 exponent: Option<i16>,
424 data: &AggregatedPriceFeedData,
425 properties: &[PriceFeedProperty],
426 ) -> Self {
427 let mut output = Self {
428 price_feed_id,
429 price: None,
430 best_bid_price: None,
431 best_ask_price: None,
432 publisher_count: None,
433 exponent: None,
434 confidence: None,
435 funding_rate: None,
436 funding_timestamp: None,
437 funding_rate_interval: None,
438 };
439 for &property in properties {
440 match property {
441 PriceFeedProperty::Price => {
442 output.price = data.price;
443 }
444 PriceFeedProperty::BestBidPrice => {
445 output.best_bid_price = data.best_bid_price;
446 }
447 PriceFeedProperty::BestAskPrice => {
448 output.best_ask_price = data.best_ask_price;
449 }
450 PriceFeedProperty::PublisherCount => {
451 output.publisher_count = Some(data.publisher_count);
452 }
453 PriceFeedProperty::Exponent => {
454 output.exponent = exponent;
455 }
456 PriceFeedProperty::Confidence => {
457 output.confidence = data.confidence;
458 }
459 PriceFeedProperty::FundingRate => {
460 output.funding_rate = data.funding_rate;
461 }
462 PriceFeedProperty::FundingTimestamp => {
463 output.funding_timestamp = data.funding_timestamp;
464 }
465 PriceFeedProperty::FundingRateInterval => {
466 output.funding_rate_interval = data.funding_rate_interval;
467 }
468 }
469 }
470 output
471 }
472
473 pub fn new_full(
474 price_feed_id: PriceFeedId,
475 exponent: Option<i16>,
476 data: &AggregatedPriceFeedData,
477 ) -> Self {
478 Self {
479 price_feed_id,
480 price: data.price,
481 best_bid_price: data.best_bid_price,
482 best_ask_price: data.best_ask_price,
483 publisher_count: Some(data.publisher_count),
484 exponent,
485 confidence: data.confidence,
486 funding_rate: data.funding_rate,
487 funding_timestamp: data.funding_timestamp,
488 funding_rate_interval: data.funding_rate_interval,
489 }
490 }
491}
492
493#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
495#[serde(tag = "type")]
496#[serde(rename_all = "camelCase")]
497pub enum WsRequest {
498 Subscribe(SubscribeRequest),
499 Unsubscribe(UnsubscribeRequest),
500}
501
502#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
503pub struct SubscriptionId(pub u64);
504
505#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
506#[serde(rename_all = "camelCase")]
507pub struct SubscribeRequest {
508 pub subscription_id: SubscriptionId,
509 #[serde(flatten)]
510 pub params: SubscriptionParams,
511}
512
513#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
514#[serde(rename_all = "camelCase")]
515pub struct UnsubscribeRequest {
516 pub subscription_id: SubscriptionId,
517}
518
519#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, From)]
521#[serde(tag = "type")]
522#[serde(rename_all = "camelCase")]
523pub enum WsResponse {
524 Error(ErrorResponse),
525 Subscribed(SubscribedResponse),
526 SubscribedWithInvalidFeedIdsIgnored(SubscribedWithInvalidFeedIdsIgnoredResponse),
527 Unsubscribed(UnsubscribedResponse),
528 SubscriptionError(SubscriptionErrorResponse),
529 StreamUpdated(StreamUpdatedResponse),
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
534#[serde(rename_all = "camelCase")]
535pub struct SubscribedResponse {
536 pub subscription_id: SubscriptionId,
537}
538
539#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
540#[serde(rename_all = "camelCase")]
541pub struct InvalidFeedSubscriptionDetails {
542 pub unknown_ids: Vec<PriceFeedId>,
543 pub unknown_symbols: Vec<String>,
544 pub unsupported_channels: Vec<PriceFeedId>,
545 pub unstable: Vec<PriceFeedId>,
546}
547
548#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
549#[serde(rename_all = "camelCase")]
550pub struct SubscribedWithInvalidFeedIdsIgnoredResponse {
551 pub subscription_id: SubscriptionId,
552 pub subscribed_feed_ids: Vec<PriceFeedId>,
553 pub ignored_invalid_feed_ids: InvalidFeedSubscriptionDetails,
554}
555
556#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
557#[serde(rename_all = "camelCase")]
558pub struct UnsubscribedResponse {
559 pub subscription_id: SubscriptionId,
560}
561
562#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
565#[serde(rename_all = "camelCase")]
566pub struct SubscriptionErrorResponse {
567 pub subscription_id: SubscriptionId,
568 pub error: String,
569}
570
571#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
574#[serde(rename_all = "camelCase")]
575pub struct ErrorResponse {
576 pub error: String,
577}
578
579#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
582#[serde(rename_all = "camelCase")]
583pub struct StreamUpdatedResponse {
584 pub subscription_id: SubscriptionId,
585 #[serde(flatten)]
586 pub payload: JsonUpdate,
587}
588
589fn validate_price_feed_ids_or_symbols(
591 price_feed_ids: &Option<Vec<PriceFeedId>>,
592 symbols: &Option<Vec<String>>,
593) -> Result<(), &'static str> {
594 if price_feed_ids.is_none() && symbols.is_none() {
595 return Err("either price feed ids or symbols must be specified");
596 }
597 if price_feed_ids.is_some() && symbols.is_some() {
598 return Err("either price feed ids or symbols must be specified, not both");
599 }
600 Ok(())
601}
602
603fn validate_optional_nonempty_vec_has_unique_elements<T>(
604 vec: &Option<Vec<T>>,
605 empty_msg: &'static str,
606 duplicate_msg: &'static str,
607) -> Result<(), &'static str>
608where
609 T: Eq + std::hash::Hash,
610{
611 if let Some(ref items) = vec {
612 if items.is_empty() {
613 return Err(empty_msg);
614 }
615 if !items.iter().all_unique() {
616 return Err(duplicate_msg);
617 }
618 }
619 Ok(())
620}
621
622fn validate_properties(properties: &[PriceFeedProperty]) -> Result<(), &'static str> {
623 if properties.is_empty() {
624 return Err("no properties specified");
625 }
626 if !properties.iter().all_unique() {
627 return Err("duplicate properties specified");
628 }
629 Ok(())
630}
631
632fn validate_formats(formats: &[Format]) -> Result<(), &'static str> {
633 if !formats.iter().all_unique() {
634 return Err("duplicate formats or chains specified");
635 }
636 Ok(())
637}