1use std::{
2 cmp::Ordering,
3 fmt::Display,
4 ops::{Deref, DerefMut},
5};
6
7use derive_more::From;
8use itertools::Itertools as _;
9use serde::{de::Error, Deserialize, Serialize};
10use serde_with::{hex::Hex, serde_as};
11
12use crate::{
13 payload::AggregatedPriceFeedData,
14 time::{DurationUs, FixedRate, TimestampUs},
15 ChannelId, Price, PriceFeedId, PriceFeedProperty, Rate,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
21#[cfg_attr(feature = "utoipa", schema(examples(LatestPriceRequestRepr::example1)))]
22pub struct LatestPriceRequestRepr {
23 pub price_feed_ids: Option<Vec<PriceFeedId>>,
26 pub symbols: Option<Vec<String>>,
29 pub properties: Vec<PriceFeedProperty>,
31 #[serde(alias = "chains")]
34 pub formats: Vec<Format>,
35 #[serde(default)]
36 pub json_binary_encoding: JsonBinaryEncoding,
37 #[serde(default = "default_parsed")]
40 pub parsed: bool,
41 pub channel: Channel,
43}
44
45#[cfg(feature = "utoipa")]
46impl LatestPriceRequestRepr {
47 fn example1() -> Self {
48 Self {
49 price_feed_ids: None,
50 symbols: Some(vec!["Crypto.BTC/USD".into()]),
51 properties: vec![PriceFeedProperty::Price, PriceFeedProperty::Confidence],
52 formats: vec![Format::Evm],
53 json_binary_encoding: JsonBinaryEncoding::Hex,
54 parsed: true,
55 channel: Channel::RealTime,
56 }
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
61#[serde(rename_all = "camelCase")]
62#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
63pub struct LatestPriceRequest(LatestPriceRequestRepr);
64
65impl<'de> Deserialize<'de> for LatestPriceRequest {
66 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67 where
68 D: serde::Deserializer<'de>,
69 {
70 let value = LatestPriceRequestRepr::deserialize(deserializer)?;
71 Self::new(value).map_err(Error::custom)
72 }
73}
74
75impl LatestPriceRequest {
76 pub fn new(value: LatestPriceRequestRepr) -> Result<Self, &'static str> {
77 validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
78 validate_optional_nonempty_vec_has_unique_elements(
79 &value.price_feed_ids,
80 "no price feed ids specified",
81 "duplicate price feed ids specified",
82 )?;
83 validate_optional_nonempty_vec_has_unique_elements(
84 &value.symbols,
85 "no symbols specified",
86 "duplicate symbols specified",
87 )?;
88 validate_formats(&value.formats)?;
89 validate_properties(&value.properties)?;
90 Ok(Self(value))
91 }
92}
93
94impl Deref for LatestPriceRequest {
95 type Target = LatestPriceRequestRepr;
96
97 fn deref(&self) -> &Self::Target {
98 &self.0
99 }
100}
101impl DerefMut for LatestPriceRequest {
102 fn deref_mut(&mut self) -> &mut Self::Target {
103 &mut self.0
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
110pub struct PriceRequestRepr {
111 pub timestamp: TimestampUs,
113 pub price_feed_ids: Option<Vec<PriceFeedId>>,
116 #[cfg_attr(feature = "utoipa", schema(default))]
119 pub symbols: Option<Vec<String>>,
120 pub properties: Vec<PriceFeedProperty>,
122 pub formats: Vec<Format>,
124 #[serde(default)]
125 pub json_binary_encoding: JsonBinaryEncoding,
126 #[serde(default = "default_parsed")]
129 pub parsed: bool,
130 pub channel: Channel,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
135#[serde(rename_all = "camelCase")]
136#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
137pub struct PriceRequest(PriceRequestRepr);
138
139impl<'de> Deserialize<'de> for PriceRequest {
140 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
141 where
142 D: serde::Deserializer<'de>,
143 {
144 let value = PriceRequestRepr::deserialize(deserializer)?;
145 Self::new(value).map_err(Error::custom)
146 }
147}
148
149impl PriceRequest {
150 pub fn new(value: PriceRequestRepr) -> Result<Self, &'static str> {
151 validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
152 validate_optional_nonempty_vec_has_unique_elements(
153 &value.price_feed_ids,
154 "no price feed ids specified",
155 "duplicate price feed ids specified",
156 )?;
157 validate_optional_nonempty_vec_has_unique_elements(
158 &value.symbols,
159 "no symbols specified",
160 "duplicate symbols specified",
161 )?;
162 validate_formats(&value.formats)?;
163 validate_properties(&value.properties)?;
164 Ok(Self(value))
165 }
166}
167
168impl Deref for PriceRequest {
169 type Target = PriceRequestRepr;
170
171 fn deref(&self) -> &Self::Target {
172 &self.0
173 }
174}
175impl DerefMut for PriceRequest {
176 fn deref_mut(&mut self) -> &mut Self::Target {
177 &mut self.0
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
182#[serde(rename_all = "camelCase")]
183#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
184pub struct ReducePriceRequest {
185 pub payload: JsonUpdate,
188 pub price_feed_ids: Vec<PriceFeedId>,
190}
191
192pub type LatestPriceResponse = JsonUpdate;
193pub type ReducePriceResponse = JsonUpdate;
194pub type PriceResponse = JsonUpdate;
195
196pub fn default_parsed() -> bool {
197 true
198}
199
200pub fn schema_default_symbols() -> Option<Vec<String>> {
201 None
202}
203pub fn schema_default_price_feed_ids() -> Option<Vec<PriceFeedId>> {
204 Some(vec![PriceFeedId(1)])
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
210pub enum DeliveryFormat {
211 #[default]
213 Json,
214 Binary,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
219#[serde(rename_all = "camelCase")]
220#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
221pub enum Format {
222 Evm,
223 Solana,
224 LeEcdsa,
225 LeUnsigned,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
229#[serde(rename_all = "camelCase")]
230#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
231pub enum JsonBinaryEncoding {
232 #[default]
233 Base64,
234 Hex,
235}
236
237#[derive(Serialize, Deserialize)]
238#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
239pub enum ChannelSchemaRepr {
240 #[serde(rename = "real_time")]
241 RealTime,
242 #[serde(rename = "fixed_rate@50ms")]
243 FixedRate50ms,
244 #[serde(rename = "fixed_rate@200ms")]
245 FixedRate200ms,
246 #[serde(rename = "fixed_rate@1000ms")]
247 FixedRate1000ms,
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, From)]
251pub enum Channel {
252 FixedRate(FixedRate),
253 RealTime,
254}
255
256#[cfg(feature = "utoipa")]
257impl utoipa::PartialSchema for Channel {
258 fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
259 ChannelSchemaRepr::schema()
260 }
261}
262
263#[cfg(feature = "utoipa")]
264impl utoipa::ToSchema for Channel {
265 fn name() -> std::borrow::Cow<'static, str> {
266 ChannelSchemaRepr::name()
267 }
268
269 fn schemas(
270 schemas: &mut Vec<(
271 String,
272 utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>,
273 )>,
274 ) {
275 ChannelSchemaRepr::schemas(schemas)
276 }
277}
278
279impl PartialOrd for Channel {
280 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
281 let rate_left = match self {
282 Channel::FixedRate(rate) => rate.duration().as_micros(),
283 Channel::RealTime => FixedRate::MIN.duration().as_micros(),
284 };
285 let rate_right = match other {
286 Channel::FixedRate(rate) => rate.duration().as_micros(),
287 Channel::RealTime => FixedRate::MIN.duration().as_micros(),
288 };
289 Some(rate_left.cmp(&rate_right))
290 }
291}
292
293impl Serialize for Channel {
294 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
295 where
296 S: serde::Serializer,
297 {
298 match self {
299 Channel::FixedRate(fixed_rate) => serializer.serialize_str(&format!(
300 "fixed_rate@{}ms",
301 fixed_rate.duration().as_millis()
302 )),
303 Channel::RealTime => serializer.serialize_str("real_time"),
304 }
305 }
306}
307
308impl Display for Channel {
309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 match self {
311 Channel::FixedRate(fixed_rate) => {
312 write!(f, "fixed_rate@{}ms", fixed_rate.duration().as_millis())
313 }
314 Channel::RealTime => write!(f, "real_time"),
315 }
316 }
317}
318
319impl Channel {
320 pub fn id(&self) -> ChannelId {
321 match self {
322 Channel::FixedRate(fixed_rate) => match fixed_rate.duration().as_millis() {
323 50 => ChannelId::FIXED_RATE_50,
324 200 => ChannelId::FIXED_RATE_200,
325 1000 => ChannelId::FIXED_RATE_1000,
326 _ => panic!("unknown channel: {self:?}"),
327 },
328 Channel::RealTime => ChannelId::REAL_TIME,
329 }
330 }
331}
332
333impl TryFrom<ChannelId> for Channel {
334 type Error = ChannelId;
335
336 fn try_from(id: ChannelId) -> Result<Self, Self::Error> {
337 match id {
338 ChannelId::REAL_TIME => Ok(Channel::RealTime),
339 ChannelId::FIXED_RATE_50 => Ok(Channel::FixedRate(FixedRate::RATE_50_MS)),
340 ChannelId::FIXED_RATE_200 => Ok(Channel::FixedRate(FixedRate::RATE_200_MS)),
341 ChannelId::FIXED_RATE_1000 => Ok(Channel::FixedRate(FixedRate::RATE_1000_MS)),
342 _ => Err(id),
343 }
344 }
345}
346
347#[test]
348fn id_supports_all_fixed_rates() {
349 for rate in FixedRate::ALL {
350 Channel::FixedRate(rate).id();
351 }
352}
353
354#[test]
355fn from_id_round_trips_with_id() {
356 let all_channels = [
357 Channel::RealTime,
358 Channel::FixedRate(FixedRate::RATE_50_MS),
359 Channel::FixedRate(FixedRate::RATE_200_MS),
360 Channel::FixedRate(FixedRate::RATE_1000_MS),
361 ];
362 for channel in all_channels {
363 assert_eq!(Channel::try_from(channel.id()), Ok(channel));
364 }
365}
366
367#[test]
368fn from_id_returns_none_for_unknown_ids() {
369 assert!(Channel::try_from(ChannelId(0)).is_err());
370 assert!(Channel::try_from(ChannelId(5)).is_err());
371 assert!(Channel::try_from(ChannelId(255)).is_err());
372}
373
374#[test]
375fn parse_channel_accepts_numeric_ids() {
376 assert_eq!(parse_channel("1"), Some(Channel::RealTime));
377 assert_eq!(
378 parse_channel("2"),
379 Some(Channel::FixedRate(FixedRate::RATE_50_MS))
380 );
381 assert_eq!(
382 parse_channel("3"),
383 Some(Channel::FixedRate(FixedRate::RATE_200_MS))
384 );
385 assert_eq!(
386 parse_channel("4"),
387 Some(Channel::FixedRate(FixedRate::RATE_1000_MS))
388 );
389}
390
391#[test]
392fn parse_channel_rejects_invalid_numeric_ids() {
393 assert_eq!(parse_channel("0"), None);
394 assert_eq!(parse_channel("5"), None); assert_eq!(parse_channel("99"), None);
396}
397
398#[test]
399fn channel_deserializes_from_json_string() {
400 let channel: Channel = serde_json::from_str(r#""3""#).unwrap();
401 assert_eq!(channel, Channel::FixedRate(FixedRate::RATE_200_MS));
402
403 let channel: Channel = serde_json::from_str(r#""fixed_rate@200ms""#).unwrap();
404 assert_eq!(channel, Channel::FixedRate(FixedRate::RATE_200_MS));
405}
406
407#[test]
408fn channel_deserializes_from_json_number() {
409 let channel: Channel = serde_json::from_str("3").unwrap();
410 assert_eq!(channel, Channel::FixedRate(FixedRate::RATE_200_MS));
411
412 let channel: Channel = serde_json::from_str("1").unwrap();
413 assert_eq!(channel, Channel::RealTime);
414}
415
416#[test]
417fn channel_rejects_invalid_json_number() {
418 assert!(serde_json::from_str::<Channel>("0").is_err());
419 assert!(serde_json::from_str::<Channel>("5").is_err());
420 assert!(serde_json::from_str::<Channel>("999").is_err());
421}
422
423fn parse_channel(value: &str) -> Option<Channel> {
424 if value == "real_time" {
425 Some(Channel::RealTime)
426 } else if let Some(rest) = value.strip_prefix("fixed_rate@") {
427 let ms_value = rest.strip_suffix("ms")?;
428 Some(Channel::FixedRate(FixedRate::from_millis(
429 ms_value.parse().ok()?,
430 )?))
431 } else if let Ok(id) = value.parse::<u8>() {
432 Channel::try_from(ChannelId(id)).ok()
433 } else {
434 None
435 }
436}
437
438impl<'de> Deserialize<'de> for Channel {
439 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
440 where
441 D: serde::Deserializer<'de>,
442 {
443 struct ChannelVisitor;
444
445 impl<'de> serde::de::Visitor<'de> for ChannelVisitor {
446 type Value = Channel;
447
448 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
449 formatter.write_str("a channel name string or numeric channel ID")
450 }
451
452 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Channel, E> {
453 parse_channel(value).ok_or_else(|| E::custom("unknown channel"))
454 }
455
456 fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Channel, E> {
457 let id = u8::try_from(value).map_err(|_| E::custom("channel ID out of range"))?;
458 Channel::try_from(ChannelId(id)).map_err(|_| E::custom("unknown channel ID"))
459 }
460 }
461
462 deserializer.deserialize_any(ChannelVisitor)
463 }
464}
465
466#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
467#[serde(rename_all = "camelCase")]
468#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
469pub struct SubscriptionParamsRepr {
470 pub price_feed_ids: Option<Vec<PriceFeedId>>,
473 #[cfg_attr(feature = "utoipa", schema(default))]
476 pub symbols: Option<Vec<String>>,
477 pub properties: Vec<PriceFeedProperty>,
479 #[serde(alias = "chains")]
483 pub formats: Vec<Format>,
484 #[serde(default)]
488 pub delivery_format: DeliveryFormat,
489 #[serde(default)]
492 pub json_binary_encoding: JsonBinaryEncoding,
493 #[serde(default = "default_parsed")]
496 pub parsed: bool,
497 pub channel: Channel,
499 #[serde(default, alias = "ignoreInvalidFeedIds")]
502 pub ignore_invalid_feeds: bool,
503}
504
505#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
506#[serde(rename_all = "camelCase")]
507#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
508pub struct SubscriptionParams(SubscriptionParamsRepr);
509
510impl<'de> Deserialize<'de> for SubscriptionParams {
511 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
512 where
513 D: serde::Deserializer<'de>,
514 {
515 let value = SubscriptionParamsRepr::deserialize(deserializer)?;
516 Self::new(value).map_err(Error::custom)
517 }
518}
519
520impl SubscriptionParams {
521 pub fn new(value: SubscriptionParamsRepr) -> Result<Self, &'static str> {
522 validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
523 validate_optional_nonempty_vec_has_unique_elements(
524 &value.price_feed_ids,
525 "no price feed ids specified",
526 "duplicate price feed ids specified",
527 )?;
528 validate_optional_nonempty_vec_has_unique_elements(
529 &value.symbols,
530 "no symbols specified",
531 "duplicate symbols specified",
532 )?;
533 validate_formats(&value.formats)?;
534 validate_properties(&value.properties)?;
535 Ok(Self(value))
536 }
537}
538
539impl Deref for SubscriptionParams {
540 type Target = SubscriptionParamsRepr;
541
542 fn deref(&self) -> &Self::Target {
543 &self.0
544 }
545}
546impl DerefMut for SubscriptionParams {
547 fn deref_mut(&mut self) -> &mut Self::Target {
548 &mut self.0
549 }
550}
551
552#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
553#[serde(rename_all = "camelCase")]
554#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
555pub struct JsonBinaryData {
556 pub encoding: JsonBinaryEncoding,
558 pub data: String,
560}
561
562#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
563#[serde(rename_all = "camelCase")]
564#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
565pub struct JsonUpdate {
566 #[serde(skip_serializing_if = "Option::is_none")]
569 pub parsed: Option<ParsedPayload>,
570 #[serde(skip_serializing_if = "Option::is_none")]
572 pub evm: Option<JsonBinaryData>,
573 #[serde(skip_serializing_if = "Option::is_none")]
575 pub solana: Option<JsonBinaryData>,
576 #[serde(skip_serializing_if = "Option::is_none")]
578 pub le_ecdsa: Option<JsonBinaryData>,
579 #[serde(skip_serializing_if = "Option::is_none")]
581 pub le_unsigned: Option<JsonBinaryData>,
582}
583
584#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
585#[serde(rename_all = "camelCase")]
586#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
587pub struct ParsedPayload {
588 #[serde(with = "crate::serde_str::timestamp")]
590 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
591 pub timestamp_us: TimestampUs,
592 pub price_feeds: Vec<ParsedFeedPayload>,
594}
595
596#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
598#[serde(rename_all = "camelCase")]
599#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
600pub struct ParsedFeedPayload {
601 pub price_feed_id: PriceFeedId,
603 #[serde(skip_serializing_if = "Option::is_none")]
607 #[serde(with = "crate::serde_str::option_price")]
608 #[serde(default)]
609 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
610 pub price: Option<Price>,
611 #[serde(skip_serializing_if = "Option::is_none")]
615 #[serde(with = "crate::serde_str::option_price")]
616 #[serde(default)]
617 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
618 pub best_bid_price: Option<Price>,
619 #[serde(skip_serializing_if = "Option::is_none")]
623 #[serde(with = "crate::serde_str::option_price")]
624 #[serde(default)]
625 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
626 pub best_ask_price: Option<Price>,
627 #[serde(skip_serializing_if = "Option::is_none")]
630 #[serde(default)]
631 pub publisher_count: Option<u16>,
632 #[serde(skip_serializing_if = "Option::is_none")]
637 #[serde(default)]
638 pub exponent: Option<i16>,
639 #[serde(skip_serializing_if = "Option::is_none")]
643 #[serde(default)]
644 pub confidence: Option<Price>,
645 #[serde(skip_serializing_if = "Option::is_none")]
650 #[serde(default)]
651 pub funding_rate: Option<Rate>,
652 #[serde(skip_serializing_if = "Option::is_none")]
657 #[serde(default)]
658 pub funding_timestamp: Option<TimestampUs>,
659 #[serde(skip_serializing_if = "Option::is_none")]
663 #[serde(default)]
664 pub funding_rate_interval: Option<DurationUs>,
665 #[serde(skip_serializing_if = "Option::is_none")]
668 #[serde(default)]
669 pub market_session: Option<MarketSession>,
670 #[serde(skip_serializing_if = "Option::is_none")]
675 #[serde(with = "crate::serde_str::option_price")]
676 #[serde(default)]
677 #[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
678 pub ema_price: Option<Price>,
679 #[serde(skip_serializing_if = "Option::is_none")]
684 #[serde(default)]
685 pub ema_confidence: Option<Price>,
686 #[serde(skip_serializing_if = "Option::is_none")]
687 #[serde(default)]
688 pub feed_update_timestamp: Option<TimestampUs>,
689 }
691
692impl ParsedFeedPayload {
693 pub fn new(
694 price_feed_id: PriceFeedId,
695 data: &AggregatedPriceFeedData,
696 properties: &[PriceFeedProperty],
697 ) -> Self {
698 let mut output = Self {
699 price_feed_id,
700 price: None,
701 best_bid_price: None,
702 best_ask_price: None,
703 publisher_count: None,
704 exponent: None,
705 confidence: None,
706 funding_rate: None,
707 funding_timestamp: None,
708 funding_rate_interval: None,
709 market_session: None,
710 ema_price: None,
711 ema_confidence: None,
712 feed_update_timestamp: None,
713 };
714 for &property in properties {
715 match property {
716 PriceFeedProperty::Price => {
717 output.price = data.price;
718 }
719 PriceFeedProperty::BestBidPrice => {
720 output.best_bid_price = data.best_bid_price;
721 }
722 PriceFeedProperty::BestAskPrice => {
723 output.best_ask_price = data.best_ask_price;
724 }
725 PriceFeedProperty::PublisherCount => {
726 output.publisher_count = Some(data.publisher_count);
727 }
728 PriceFeedProperty::Exponent => {
729 output.exponent = Some(data.exponent);
730 }
731 PriceFeedProperty::Confidence => {
732 output.confidence = data.confidence;
733 }
734 PriceFeedProperty::FundingRate => {
735 output.funding_rate = data.funding_rate;
736 }
737 PriceFeedProperty::FundingTimestamp => {
738 output.funding_timestamp = data.funding_timestamp;
739 }
740 PriceFeedProperty::FundingRateInterval => {
741 output.funding_rate_interval = data.funding_rate_interval;
742 }
743 PriceFeedProperty::MarketSession => {
744 output.market_session = Some(data.market_session);
745 }
746 PriceFeedProperty::EmaPrice => {
747 output.ema_price = data.ema_price;
748 }
749 PriceFeedProperty::EmaConfidence => {
750 output.ema_confidence = data.ema_confidence;
751 }
752 PriceFeedProperty::FeedUpdateTimestamp => {
753 output.feed_update_timestamp = data.feed_update_timestamp;
754 }
755 }
756 }
757 output
758 }
759
760 pub fn new_full(
761 price_feed_id: PriceFeedId,
762 exponent: Option<i16>,
763 data: &AggregatedPriceFeedData,
764 ) -> Self {
765 Self {
766 price_feed_id,
767 price: data.price,
768 best_bid_price: data.best_bid_price,
769 best_ask_price: data.best_ask_price,
770 publisher_count: Some(data.publisher_count),
771 exponent,
772 confidence: data.confidence,
773 funding_rate: data.funding_rate,
774 funding_timestamp: data.funding_timestamp,
775 funding_rate_interval: data.funding_rate_interval,
776 market_session: Some(data.market_session),
777 ema_price: data.ema_price,
778 ema_confidence: data.ema_confidence,
779 feed_update_timestamp: data.feed_update_timestamp,
780 }
781 }
782}
783
784#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
786#[serde(tag = "type")]
787#[serde(rename_all = "camelCase")]
788#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
789pub enum WsRequest {
790 Subscribe(SubscribeRequest),
791 Unsubscribe(UnsubscribeRequest),
792}
793
794#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
795#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
796pub struct SubscriptionId(pub u64);
797
798#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
806#[serde(rename_all = "camelCase")]
807#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
808pub struct SubscribeRequest {
809 pub subscription_id: SubscriptionId,
812 #[serde(flatten)]
814 pub params: SubscriptionParams,
815}
816
817#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
824#[serde(rename_all = "camelCase")]
825#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
826pub struct UnsubscribeRequest {
827 pub subscription_id: SubscriptionId,
829}
830
831#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, From)]
833#[serde(tag = "type")]
834#[serde(rename_all = "camelCase")]
835#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
836pub enum WsResponse {
837 Error(ErrorResponse),
838 Subscribed(SubscribedResponse),
839 SubscribedWithInvalidFeedIdsIgnored(SubscribedWithInvalidFeedIdsIgnoredResponse),
840 Unsubscribed(UnsubscribedResponse),
841 SubscriptionError(SubscriptionErrorResponse),
842 StreamUpdated(StreamUpdatedResponse),
843}
844
845#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
847#[serde(rename_all = "camelCase")]
848#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
849pub struct SubscribedResponse {
850 pub subscription_id: SubscriptionId,
851}
852
853#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
854#[serde(rename_all = "camelCase")]
855#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
856pub struct InvalidFeedSubscriptionDetails {
857 pub unknown_ids: Vec<PriceFeedId>,
859 pub unknown_symbols: Vec<String>,
861 pub unsupported_channels: Vec<PriceFeedId>,
863 pub unstable: Vec<PriceFeedId>,
865 #[serde(default)]
867 pub not_entitled: Vec<PriceFeedId>,
868}
869
870#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
873#[serde(rename_all = "camelCase")]
874#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
875pub struct SubscribedWithInvalidFeedIdsIgnoredResponse {
876 pub subscription_id: SubscriptionId,
878 pub subscribed_feed_ids: Vec<PriceFeedId>,
880 pub ignored_invalid_feed_ids: InvalidFeedSubscriptionDetails,
882}
883
884#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
886#[serde(rename_all = "camelCase")]
887#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
888pub struct UnsubscribedResponse {
889 pub subscription_id: SubscriptionId,
891}
892
893#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
896#[serde(rename_all = "camelCase")]
897#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
898pub struct SubscriptionErrorResponse {
899 pub subscription_id: SubscriptionId,
901 pub error: String,
903}
904
905#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
908#[serde(rename_all = "camelCase")]
909#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
910pub struct ErrorResponse {
911 pub error: String,
913}
914
915#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
918#[serde(rename_all = "camelCase")]
919#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
920pub struct StreamUpdatedResponse {
921 pub subscription_id: SubscriptionId,
923 #[serde(flatten)]
925 pub payload: JsonUpdate,
926}
927
928fn validate_price_feed_ids_or_symbols(
930 price_feed_ids: &Option<Vec<PriceFeedId>>,
931 symbols: &Option<Vec<String>>,
932) -> Result<(), &'static str> {
933 if price_feed_ids.is_none() && symbols.is_none() {
934 return Err("either price feed ids or symbols must be specified");
935 }
936 if price_feed_ids.is_some() && symbols.is_some() {
937 return Err("either price feed ids or symbols must be specified, not both");
938 }
939 Ok(())
940}
941
942fn validate_optional_nonempty_vec_has_unique_elements<T>(
943 vec: &Option<Vec<T>>,
944 empty_msg: &'static str,
945 duplicate_msg: &'static str,
946) -> Result<(), &'static str>
947where
948 T: Eq + std::hash::Hash,
949{
950 if let Some(items) = vec {
951 if items.is_empty() {
952 return Err(empty_msg);
953 }
954 if !items.iter().all_unique() {
955 return Err(duplicate_msg);
956 }
957 }
958 Ok(())
959}
960
961fn validate_properties(properties: &[PriceFeedProperty]) -> Result<(), &'static str> {
962 if properties.is_empty() {
963 return Err("no properties specified");
964 }
965 if !properties.iter().all_unique() {
966 return Err("duplicate properties specified");
967 }
968 Ok(())
969}
970
971fn validate_formats(formats: &[Format]) -> Result<(), &'static str> {
972 if !formats.iter().all_unique() {
973 return Err("duplicate formats or chains specified");
974 }
975 Ok(())
976}
977
978#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, From, Default)]
979#[serde(rename_all = "camelCase")]
980#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
981#[cfg_attr(feature = "utoipa", schema(example = "regular"))]
982pub enum MarketSession {
983 #[default]
984 Regular,
985 PreMarket,
986 PostMarket,
987 OverNight,
988 Closed,
989}
990
991#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, From, Default)]
992#[serde(rename_all = "camelCase")]
993#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
994#[cfg_attr(feature = "utoipa", schema(example = "open"))]
995pub enum TradingStatus {
996 #[default]
997 Open,
998 Closed,
999 Halted,
1000 CorpAction,
1001}
1002
1003impl From<MarketSession> for i16 {
1004 fn from(s: MarketSession) -> i16 {
1005 match s {
1006 MarketSession::Regular => 0,
1007 MarketSession::PreMarket => 1,
1008 MarketSession::PostMarket => 2,
1009 MarketSession::OverNight => 3,
1010 MarketSession::Closed => 4,
1011 }
1012 }
1013}
1014
1015impl TryFrom<i16> for MarketSession {
1016 type Error = anyhow::Error;
1017
1018 fn try_from(value: i16) -> Result<MarketSession, Self::Error> {
1019 match value {
1020 0 => Ok(MarketSession::Regular),
1021 1 => Ok(MarketSession::PreMarket),
1022 2 => Ok(MarketSession::PostMarket),
1023 3 => Ok(MarketSession::OverNight),
1024 4 => Ok(MarketSession::Closed),
1025 _ => Err(anyhow::anyhow!("invalid MarketSession value: {}", value)),
1026 }
1027 }
1028}
1029
1030pub type GuardianIndex = u8;
1031pub type Slot = u64;
1032pub type MerkleTimestamp = u32;
1033pub type RawMerkleRoot = [u8; 20];
1034pub type RawMerkleSignature = [u8; 65];
1035pub type MerklePriceFeedId = [u8; 32];
1036pub type RawMerkleMessage = Vec<u8>;
1037
1038#[serde_as]
1039#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1040#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1041pub struct SignedMerkleRoot {
1042 #[serde_as(as = "Hex")]
1044 #[cfg_attr(feature = "utoipa", schema(value_type = String, example = "0x1a2b3c..."))]
1045 pub root: Vec<u8>,
1046
1047 pub slot: Slot,
1048 pub timestamp: u32,
1049 pub channel: Channel,
1050
1051 #[serde_as(as = "Hex")]
1053 #[cfg_attr(feature = "utoipa", schema(value_type = String, example = "0x1a2b3c..."))]
1054 pub signature: Vec<u8>,
1055
1056 #[serde_as(as = "Vec<Hex>")]
1057 #[cfg_attr(feature = "utoipa", schema(value_type = Vec<String>, example = json!(["00abcdef...", "00123456..."])))]
1058 pub messages: Vec<RawMerkleMessage>,
1059}
1060
1061#[serde_as]
1062#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1063#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1064pub struct SignedGuardianSetUpgrade {
1065 pub current_guardian_set_index: u32,
1067 pub new_guardian_set_index: u32,
1069 #[serde_as(as = "Vec<Hex>")]
1071 pub new_guardian_keys: Vec<Vec<u8>>,
1072 #[serde_as(as = "Hex")]
1074 #[cfg_attr(feature = "utoipa", schema(value_type = String, example = "0x1a2b3c..."))]
1075 pub body: Vec<u8>,
1076 #[serde_as(as = "Hex")]
1078 #[cfg_attr(feature = "utoipa", schema(value_type = String, example = "0x1a2b3c..."))]
1079 pub signature: Vec<u8>,
1080}
1081
1082#[cfg(test)]
1083mod tests {
1084 use super::*;
1085 use serde_json::json;
1086
1087 #[test]
1088 fn signed_merkle_root_json_serialization() {
1089 let root = SignedMerkleRoot {
1090 root: vec![
1091 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1092 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
1093 ],
1094 slot: 34567890123,
1095 timestamp: 1700000000,
1096 channel: Channel::FixedRate(FixedRate::RATE_200_MS),
1097 signature: vec![0xaa; 65],
1098 messages: vec![vec![0x00, 0xab, 0xcd, 0xef], vec![0x00, 0x12, 0x34, 0x56]],
1099 };
1100
1101 let json = serde_json::to_value(&root).unwrap();
1102
1103 assert_eq!(json["root"], "0102030405060708090a0b0c0d0e0f1011121314");
1105 assert_eq!(json["slot"], 34567890123u64);
1106 assert_eq!(json["timestamp"], 1700000000u32);
1107 assert_eq!(
1108 json["channel"],
1109 Channel::FixedRate(FixedRate::RATE_200_MS).to_string()
1110 );
1111 assert_eq!(json["signature"], "aa".repeat(65));
1112 assert_eq!(json["messages"], json!(["00abcdef", "00123456"]));
1113
1114 let deserialized: SignedMerkleRoot = serde_json::from_value(json).unwrap();
1116 assert_eq!(deserialized, root);
1117 }
1118
1119 #[test]
1120 fn signed_guardian_set_upgrade_json_serialization() {
1121 let upgrade = SignedGuardianSetUpgrade {
1122 current_guardian_set_index: 4,
1123 new_guardian_set_index: 5,
1124 new_guardian_keys: vec![vec![0x11; 20], vec![0x22; 20]],
1125 body: vec![0xde, 0xad, 0xbe, 0xef],
1126 signature: vec![0xaa; 65],
1127 };
1128
1129 let json = serde_json::to_value(&upgrade).unwrap();
1130
1131 assert_eq!(json["current_guardian_set_index"], 4);
1132 assert_eq!(json["new_guardian_set_index"], 5);
1133 let keys = json["new_guardian_keys"].as_array().unwrap();
1134 assert_eq!(keys.len(), 2);
1135 assert_eq!(keys[0], "11".repeat(20));
1136 assert_eq!(keys[1], "22".repeat(20));
1137 assert_eq!(json["body"], "deadbeef");
1138 assert_eq!(json["signature"], "aa".repeat(65));
1139
1140 let deserialized: SignedGuardianSetUpgrade = serde_json::from_value(json).unwrap();
1142 assert_eq!(deserialized, upgrade);
1143 }
1144}