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};
10use utoipa::ToSchema;
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, ToSchema)]
19#[serde(rename_all = "camelCase")]
20pub struct LatestPriceRequestRepr {
21 #[schema(example = json!([1]))]
23 pub price_feed_ids: Option<Vec<PriceFeedId>>,
24 #[schema(example = schema_default_symbols)]
25 pub symbols: Option<Vec<String>>,
26 pub properties: Vec<PriceFeedProperty>,
27 #[serde(alias = "chains")]
29 pub formats: Vec<Format>,
30 #[serde(default)]
31 pub json_binary_encoding: JsonBinaryEncoding,
32 #[serde(default = "default_parsed")]
35 pub parsed: bool,
36 pub channel: Channel,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, ToSchema)]
40#[serde(rename_all = "camelCase")]
41pub struct LatestPriceRequest(LatestPriceRequestRepr);
42
43impl<'de> Deserialize<'de> for LatestPriceRequest {
44 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
45 where
46 D: serde::Deserializer<'de>,
47 {
48 let value = LatestPriceRequestRepr::deserialize(deserializer)?;
49 Self::new(value).map_err(Error::custom)
50 }
51}
52
53impl LatestPriceRequest {
54 pub fn new(value: LatestPriceRequestRepr) -> Result<Self, &'static str> {
55 validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
56 validate_optional_nonempty_vec_has_unique_elements(
57 &value.price_feed_ids,
58 "no price feed ids specified",
59 "duplicate price feed ids specified",
60 )?;
61 validate_optional_nonempty_vec_has_unique_elements(
62 &value.symbols,
63 "no symbols specified",
64 "duplicate symbols specified",
65 )?;
66 validate_formats(&value.formats)?;
67 validate_properties(&value.properties)?;
68 Ok(Self(value))
69 }
70}
71
72impl Deref for LatestPriceRequest {
73 type Target = LatestPriceRequestRepr;
74
75 fn deref(&self) -> &Self::Target {
76 &self.0
77 }
78}
79impl DerefMut for LatestPriceRequest {
80 fn deref_mut(&mut self) -> &mut Self::Target {
81 &mut self.0
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
86#[serde(rename_all = "camelCase")]
87pub struct PriceRequestRepr {
88 pub timestamp: TimestampUs,
89 pub price_feed_ids: Option<Vec<PriceFeedId>>,
91 #[schema(default)]
92 pub symbols: Option<Vec<String>>,
93 pub properties: Vec<PriceFeedProperty>,
94 pub formats: Vec<Format>,
95 #[serde(default)]
96 pub json_binary_encoding: JsonBinaryEncoding,
97 #[serde(default = "default_parsed")]
100 pub parsed: bool,
101 pub channel: Channel,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, ToSchema)]
105#[serde(rename_all = "camelCase")]
106pub struct PriceRequest(PriceRequestRepr);
107
108impl<'de> Deserialize<'de> for PriceRequest {
109 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110 where
111 D: serde::Deserializer<'de>,
112 {
113 let value = PriceRequestRepr::deserialize(deserializer)?;
114 Self::new(value).map_err(Error::custom)
115 }
116}
117
118impl PriceRequest {
119 pub fn new(value: PriceRequestRepr) -> Result<Self, &'static str> {
120 validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
121 validate_optional_nonempty_vec_has_unique_elements(
122 &value.price_feed_ids,
123 "no price feed ids specified",
124 "duplicate price feed ids specified",
125 )?;
126 validate_optional_nonempty_vec_has_unique_elements(
127 &value.symbols,
128 "no symbols specified",
129 "duplicate symbols specified",
130 )?;
131 validate_formats(&value.formats)?;
132 validate_properties(&value.properties)?;
133 Ok(Self(value))
134 }
135}
136
137impl Deref for PriceRequest {
138 type Target = PriceRequestRepr;
139
140 fn deref(&self) -> &Self::Target {
141 &self.0
142 }
143}
144impl DerefMut for PriceRequest {
145 fn deref_mut(&mut self) -> &mut Self::Target {
146 &mut self.0
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
151#[serde(rename_all = "camelCase")]
152pub struct ReducePriceRequest {
153 pub payload: JsonUpdate,
154 pub price_feed_ids: Vec<PriceFeedId>,
155}
156
157pub type LatestPriceResponse = JsonUpdate;
158pub type ReducePriceResponse = JsonUpdate;
159pub type PriceResponse = JsonUpdate;
160
161pub fn default_parsed() -> bool {
162 true
163}
164
165pub fn schema_default_symbols() -> Option<Vec<String>> {
166 None
167}
168pub fn schema_default_price_feed_ids() -> Option<Vec<PriceFeedId>> {
169 Some(vec![PriceFeedId(1)])
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, ToSchema)]
173#[serde(rename_all = "camelCase")]
174pub enum DeliveryFormat {
175 #[default]
177 Json,
178 Binary,
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
183#[serde(rename_all = "camelCase")]
184pub enum Format {
185 Evm,
186 Solana,
187 LeEcdsa,
188 LeUnsigned,
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, ToSchema)]
192#[serde(rename_all = "camelCase")]
193pub enum JsonBinaryEncoding {
194 #[default]
195 Base64,
196 Hex,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, From, ToSchema)]
200#[schema(example = "fixed_rate@200ms")]
201pub enum Channel {
202 FixedRate(FixedRate),
203 #[schema(rename = "real_time")]
204 RealTime,
205}
206
207impl PartialOrd for Channel {
208 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
209 let rate_left = match self {
210 Channel::FixedRate(rate) => rate.duration().as_micros(),
211 Channel::RealTime => FixedRate::MIN.duration().as_micros(),
212 };
213 let rate_right = match other {
214 Channel::FixedRate(rate) => rate.duration().as_micros(),
215 Channel::RealTime => FixedRate::MIN.duration().as_micros(),
216 };
217 Some(rate_left.cmp(&rate_right))
218 }
219}
220
221impl Serialize for Channel {
222 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
223 where
224 S: serde::Serializer,
225 {
226 match self {
227 Channel::FixedRate(fixed_rate) => serializer.serialize_str(&format!(
228 "fixed_rate@{}ms",
229 fixed_rate.duration().as_millis()
230 )),
231 Channel::RealTime => serializer.serialize_str("real_time"),
232 }
233 }
234}
235
236impl Display for Channel {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 match self {
239 Channel::FixedRate(fixed_rate) => {
240 write!(f, "fixed_rate@{}ms", fixed_rate.duration().as_millis())
241 }
242 Channel::RealTime => write!(f, "real_time"),
243 }
244 }
245}
246
247impl Channel {
248 pub fn id(&self) -> ChannelId {
249 match self {
250 Channel::FixedRate(fixed_rate) => match fixed_rate.duration().as_millis() {
251 50 => ChannelId::FIXED_RATE_50,
252 200 => ChannelId::FIXED_RATE_200,
253 1000 => ChannelId::FIXED_RATE_1000,
254 _ => panic!("unknown channel: {self:?}"),
255 },
256 Channel::RealTime => ChannelId::REAL_TIME,
257 }
258 }
259}
260
261#[test]
262fn id_supports_all_fixed_rates() {
263 for rate in FixedRate::ALL {
264 Channel::FixedRate(rate).id();
265 }
266}
267
268fn parse_channel(value: &str) -> Option<Channel> {
269 if value == "real_time" {
270 Some(Channel::RealTime)
271 } else if let Some(rest) = value.strip_prefix("fixed_rate@") {
272 let ms_value = rest.strip_suffix("ms")?;
273 Some(Channel::FixedRate(FixedRate::from_millis(
274 ms_value.parse().ok()?,
275 )?))
276 } else {
277 None
278 }
279}
280
281impl<'de> Deserialize<'de> for Channel {
282 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
283 where
284 D: serde::Deserializer<'de>,
285 {
286 let value = <String>::deserialize(deserializer)?;
287 parse_channel(&value).ok_or_else(|| Error::custom("unknown channel"))
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
292#[serde(rename_all = "camelCase")]
293pub struct SubscriptionParamsRepr {
294 pub price_feed_ids: Option<Vec<PriceFeedId>>,
296 #[schema(default)]
297 pub symbols: Option<Vec<String>>,
298 pub properties: Vec<PriceFeedProperty>,
299 #[serde(alias = "chains")]
301 pub formats: Vec<Format>,
302 #[serde(default)]
303 pub delivery_format: DeliveryFormat,
304 #[serde(default)]
305 pub json_binary_encoding: JsonBinaryEncoding,
306 #[serde(default = "default_parsed")]
309 pub parsed: bool,
310 pub channel: Channel,
311 #[serde(default, alias = "ignoreInvalidFeedIds")]
313 pub ignore_invalid_feeds: bool,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, ToSchema)]
317#[serde(rename_all = "camelCase")]
318pub struct SubscriptionParams(SubscriptionParamsRepr);
319
320impl<'de> Deserialize<'de> for SubscriptionParams {
321 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
322 where
323 D: serde::Deserializer<'de>,
324 {
325 let value = SubscriptionParamsRepr::deserialize(deserializer)?;
326 Self::new(value).map_err(Error::custom)
327 }
328}
329
330impl SubscriptionParams {
331 pub fn new(value: SubscriptionParamsRepr) -> Result<Self, &'static str> {
332 validate_price_feed_ids_or_symbols(&value.price_feed_ids, &value.symbols)?;
333 validate_optional_nonempty_vec_has_unique_elements(
334 &value.price_feed_ids,
335 "no price feed ids specified",
336 "duplicate price feed ids specified",
337 )?;
338 validate_optional_nonempty_vec_has_unique_elements(
339 &value.symbols,
340 "no symbols specified",
341 "duplicate symbols specified",
342 )?;
343 validate_formats(&value.formats)?;
344 validate_properties(&value.properties)?;
345 Ok(Self(value))
346 }
347}
348
349impl Deref for SubscriptionParams {
350 type Target = SubscriptionParamsRepr;
351
352 fn deref(&self) -> &Self::Target {
353 &self.0
354 }
355}
356impl DerefMut for SubscriptionParams {
357 fn deref_mut(&mut self) -> &mut Self::Target {
358 &mut self.0
359 }
360}
361
362#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
363#[serde(rename_all = "camelCase")]
364pub struct JsonBinaryData {
365 pub encoding: JsonBinaryEncoding,
366 pub data: String,
367}
368
369#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
370#[serde(rename_all = "camelCase")]
371pub struct JsonUpdate {
372 #[serde(skip_serializing_if = "Option::is_none")]
374 pub parsed: Option<ParsedPayload>,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub evm: Option<JsonBinaryData>,
378 #[serde(skip_serializing_if = "Option::is_none")]
380 pub solana: Option<JsonBinaryData>,
381 #[serde(skip_serializing_if = "Option::is_none")]
383 pub le_ecdsa: Option<JsonBinaryData>,
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub le_unsigned: Option<JsonBinaryData>,
387}
388
389#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
390#[serde(rename_all = "camelCase")]
391pub struct ParsedPayload {
392 #[serde(with = "crate::serde_str::timestamp")]
393 pub timestamp_us: TimestampUs,
394 pub price_feeds: Vec<ParsedFeedPayload>,
395}
396
397#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
398#[serde(rename_all = "camelCase")]
399pub struct ParsedFeedPayload {
400 pub price_feed_id: PriceFeedId,
401 #[serde(skip_serializing_if = "Option::is_none")]
402 #[serde(with = "crate::serde_str::option_price")]
403 #[serde(default)]
404 pub price: Option<Price>,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 #[serde(with = "crate::serde_str::option_price")]
407 #[serde(default)]
408 pub best_bid_price: Option<Price>,
409 #[serde(skip_serializing_if = "Option::is_none")]
410 #[serde(with = "crate::serde_str::option_price")]
411 #[serde(default)]
412 pub best_ask_price: Option<Price>,
413 #[serde(skip_serializing_if = "Option::is_none")]
414 #[serde(default)]
415 pub publisher_count: Option<u16>,
416 #[serde(skip_serializing_if = "Option::is_none")]
417 #[serde(default)]
418 pub exponent: Option<i16>,
419 #[serde(skip_serializing_if = "Option::is_none")]
420 #[serde(default)]
421 pub confidence: Option<Price>,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 #[serde(default)]
424 pub funding_rate: Option<Rate>,
425 #[serde(skip_serializing_if = "Option::is_none")]
426 #[serde(default)]
427 pub funding_timestamp: Option<TimestampUs>,
428 #[serde(skip_serializing_if = "Option::is_none")]
430 #[serde(default)]
431 pub funding_rate_interval: Option<DurationUs>,
432 #[serde(skip_serializing_if = "Option::is_none")]
433 #[serde(default)]
434 pub market_session: Option<MarketSession>,
435 #[serde(skip_serializing_if = "Option::is_none")]
436 #[serde(with = "crate::serde_str::option_price")]
437 #[serde(default)]
438 pub ema_price: Option<Price>,
439 #[serde(skip_serializing_if = "Option::is_none")]
440 #[serde(default)]
441 pub ema_confidence: Option<Price>,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 #[serde(default)]
444 pub feed_update_timestamp: Option<TimestampUs>,
445}
446
447impl ParsedFeedPayload {
448 pub fn new(
449 price_feed_id: PriceFeedId,
450 data: &AggregatedPriceFeedData,
451 properties: &[PriceFeedProperty],
452 ) -> Self {
453 let mut output = Self {
454 price_feed_id,
455 price: None,
456 best_bid_price: None,
457 best_ask_price: None,
458 publisher_count: None,
459 exponent: None,
460 confidence: None,
461 funding_rate: None,
462 funding_timestamp: None,
463 funding_rate_interval: None,
464 market_session: None,
465 ema_price: None,
466 ema_confidence: None,
467 feed_update_timestamp: None,
468 };
469 for &property in properties {
470 match property {
471 PriceFeedProperty::Price => {
472 output.price = data.price;
473 }
474 PriceFeedProperty::BestBidPrice => {
475 output.best_bid_price = data.best_bid_price;
476 }
477 PriceFeedProperty::BestAskPrice => {
478 output.best_ask_price = data.best_ask_price;
479 }
480 PriceFeedProperty::PublisherCount => {
481 output.publisher_count = Some(data.publisher_count);
482 }
483 PriceFeedProperty::Exponent => {
484 output.exponent = Some(data.exponent);
485 }
486 PriceFeedProperty::Confidence => {
487 output.confidence = data.confidence;
488 }
489 PriceFeedProperty::FundingRate => {
490 output.funding_rate = data.funding_rate;
491 }
492 PriceFeedProperty::FundingTimestamp => {
493 output.funding_timestamp = data.funding_timestamp;
494 }
495 PriceFeedProperty::FundingRateInterval => {
496 output.funding_rate_interval = data.funding_rate_interval;
497 }
498 PriceFeedProperty::MarketSession => {
499 output.market_session = Some(data.market_session);
500 }
501 PriceFeedProperty::EmaPrice => {
502 output.ema_price = data.ema_price;
503 }
504 PriceFeedProperty::EmaConfidence => {
505 output.ema_confidence = data.ema_confidence;
506 }
507 PriceFeedProperty::FeedUpdateTimestamp => {
508 output.feed_update_timestamp = data.feed_update_timestamp;
509 }
510 }
511 }
512 output
513 }
514
515 pub fn new_full(
516 price_feed_id: PriceFeedId,
517 exponent: Option<i16>,
518 data: &AggregatedPriceFeedData,
519 ) -> Self {
520 Self {
521 price_feed_id,
522 price: data.price,
523 best_bid_price: data.best_bid_price,
524 best_ask_price: data.best_ask_price,
525 publisher_count: Some(data.publisher_count),
526 exponent,
527 confidence: data.confidence,
528 funding_rate: data.funding_rate,
529 funding_timestamp: data.funding_timestamp,
530 funding_rate_interval: data.funding_rate_interval,
531 market_session: Some(data.market_session),
532 ema_price: data.ema_price,
533 ema_confidence: data.ema_confidence,
534 feed_update_timestamp: data.feed_update_timestamp,
535 }
536 }
537}
538
539#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
541#[serde(tag = "type")]
542#[serde(rename_all = "camelCase")]
543pub enum WsRequest {
544 Subscribe(SubscribeRequest),
545 Unsubscribe(UnsubscribeRequest),
546}
547
548#[derive(
549 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, ToSchema,
550)]
551pub struct SubscriptionId(pub u64);
552
553#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
554#[serde(rename_all = "camelCase")]
555pub struct SubscribeRequest {
556 pub subscription_id: SubscriptionId,
557 #[serde(flatten)]
558 pub params: SubscriptionParams,
559}
560
561#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
562#[serde(rename_all = "camelCase")]
563pub struct UnsubscribeRequest {
564 pub subscription_id: SubscriptionId,
565}
566
567#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, From, ToSchema)]
569#[serde(tag = "type")]
570#[serde(rename_all = "camelCase")]
571pub enum WsResponse {
572 Error(ErrorResponse),
573 Subscribed(SubscribedResponse),
574 SubscribedWithInvalidFeedIdsIgnored(SubscribedWithInvalidFeedIdsIgnoredResponse),
575 Unsubscribed(UnsubscribedResponse),
576 SubscriptionError(SubscriptionErrorResponse),
577 StreamUpdated(StreamUpdatedResponse),
578}
579
580#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
582#[serde(rename_all = "camelCase")]
583pub struct SubscribedResponse {
584 pub subscription_id: SubscriptionId,
585}
586
587#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
588#[serde(rename_all = "camelCase")]
589pub struct InvalidFeedSubscriptionDetails {
590 pub unknown_ids: Vec<PriceFeedId>,
591 pub unknown_symbols: Vec<String>,
592 pub unsupported_channels: Vec<PriceFeedId>,
593 pub unstable: Vec<PriceFeedId>,
594}
595
596#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
597#[serde(rename_all = "camelCase")]
598pub struct SubscribedWithInvalidFeedIdsIgnoredResponse {
599 pub subscription_id: SubscriptionId,
600 pub subscribed_feed_ids: Vec<PriceFeedId>,
601 pub ignored_invalid_feed_ids: InvalidFeedSubscriptionDetails,
602}
603
604#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
605#[serde(rename_all = "camelCase")]
606pub struct UnsubscribedResponse {
607 pub subscription_id: SubscriptionId,
608}
609
610#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
613#[serde(rename_all = "camelCase")]
614pub struct SubscriptionErrorResponse {
615 pub subscription_id: SubscriptionId,
616 pub error: String,
617}
618
619#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
622#[serde(rename_all = "camelCase")]
623pub struct ErrorResponse {
624 pub error: String,
625}
626
627#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
630#[serde(rename_all = "camelCase")]
631pub struct StreamUpdatedResponse {
632 pub subscription_id: SubscriptionId,
633 #[serde(flatten)]
634 pub payload: JsonUpdate,
635}
636
637fn validate_price_feed_ids_or_symbols(
639 price_feed_ids: &Option<Vec<PriceFeedId>>,
640 symbols: &Option<Vec<String>>,
641) -> Result<(), &'static str> {
642 if price_feed_ids.is_none() && symbols.is_none() {
643 return Err("either price feed ids or symbols must be specified");
644 }
645 if price_feed_ids.is_some() && symbols.is_some() {
646 return Err("either price feed ids or symbols must be specified, not both");
647 }
648 Ok(())
649}
650
651fn validate_optional_nonempty_vec_has_unique_elements<T>(
652 vec: &Option<Vec<T>>,
653 empty_msg: &'static str,
654 duplicate_msg: &'static str,
655) -> Result<(), &'static str>
656where
657 T: Eq + std::hash::Hash,
658{
659 if let Some(items) = vec {
660 if items.is_empty() {
661 return Err(empty_msg);
662 }
663 if !items.iter().all_unique() {
664 return Err(duplicate_msg);
665 }
666 }
667 Ok(())
668}
669
670fn validate_properties(properties: &[PriceFeedProperty]) -> Result<(), &'static str> {
671 if properties.is_empty() {
672 return Err("no properties specified");
673 }
674 if !properties.iter().all_unique() {
675 return Err("duplicate properties specified");
676 }
677 Ok(())
678}
679
680fn validate_formats(formats: &[Format]) -> Result<(), &'static str> {
681 if !formats.iter().all_unique() {
682 return Err("duplicate formats or chains specified");
683 }
684 Ok(())
685}
686
687#[derive(
688 Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, From, ToSchema, Default,
689)]
690#[serde(rename_all = "camelCase")]
691#[schema(example = "regular")]
692pub enum MarketSession {
693 #[default]
694 Regular,
695 PreMarket,
696 PostMarket,
697 OverNight,
698 Closed,
699}
700
701#[derive(
702 Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, From, ToSchema, Default,
703)]
704#[serde(rename_all = "camelCase")]
705#[schema(example = "open")]
706pub enum TradingStatus {
707 #[default]
708 Open,
709 Closed,
710 Halted,
711 CorpAction,
712}
713
714impl From<MarketSession> for i16 {
715 fn from(s: MarketSession) -> i16 {
716 match s {
717 MarketSession::Regular => 0,
718 MarketSession::PreMarket => 1,
719 MarketSession::PostMarket => 2,
720 MarketSession::OverNight => 3,
721 MarketSession::Closed => 4,
722 }
723 }
724}
725
726impl TryFrom<i16> for MarketSession {
727 type Error = anyhow::Error;
728
729 fn try_from(value: i16) -> Result<MarketSession, Self::Error> {
730 match value {
731 0 => Ok(MarketSession::Regular),
732 1 => Ok(MarketSession::PreMarket),
733 2 => Ok(MarketSession::PostMarket),
734 3 => Ok(MarketSession::OverNight),
735 4 => Ok(MarketSession::Closed),
736 _ => Err(anyhow::anyhow!("invalid MarketSession value: {}", value)),
737 }
738 }
739}