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