1pub mod api;
5pub mod binary_update;
7mod dynamic_value;
8mod exchange_enums;
9mod feed_kind;
10pub mod hermes;
11pub mod jrpc;
13pub mod message;
15pub mod payload;
17mod price;
18pub mod publisher;
20mod rate;
21mod serde_price_as_i64;
22mod serde_str;
23mod symbol_state;
24pub mod time;
26
27use {
28 protobuf::MessageFull,
29 serde::{Deserialize, Serialize},
30};
31
32use {
33 derive_more::{From, Into},
34 strum::FromRepr,
35};
36
37pub use crate::{
38 dynamic_value::DynamicValue,
39 exchange_enums::{ExchangeAssetClass, ExchangeAssetSector, ExchangeAssetSubclass},
40 feed_kind::FeedKind,
41 price::{Price, PriceError},
42 rate::{Rate, RateError},
43 symbol_state::SymbolState,
44};
45
46#[derive(
47 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
48)]
49pub struct AssetId(pub u32);
50
51#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
52#[derive(
53 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
54)]
55#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
56pub struct ExchangeId(pub u32);
57
58#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
59#[derive(
60 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
61)]
62#[cfg_attr(feature = "utoipa", schema(value_type = u16))]
63pub struct PublisherId(pub u16);
64
65#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
66#[derive(
67 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
68)]
69#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
70pub struct PriceFeedId(pub u32);
71
72#[derive(
73 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
74)]
75pub struct ChannelId(pub u8);
76
77#[derive(
80 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
81)]
82pub struct PublisherDatapoint(pub i64);
83
84impl From<Price> for PublisherDatapoint {
85 fn from(price: Price) -> Self {
86 Self(price.mantissa_i64())
87 }
88}
89
90impl From<Rate> for PublisherDatapoint {
91 fn from(rate: Rate) -> Self {
92 Self(rate.mantissa())
93 }
94}
95
96impl ChannelId {
97 pub const REAL_TIME: ChannelId = ChannelId(1);
98 pub const FIXED_RATE_50: ChannelId = ChannelId(2);
99 pub const FIXED_RATE_200: ChannelId = ChannelId(3);
100 pub const FIXED_RATE_1000: ChannelId = ChannelId(4);
101}
102
103#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, FromRepr)]
105#[serde(rename_all = "camelCase")]
106#[repr(u8)]
107pub enum PriceFeedProperty {
108 Price,
109 BestBidPrice,
110 BestAskPrice,
111 PublisherCount,
112 Exponent,
113 Confidence,
114 FundingRate,
115 FundingTimestamp,
116 FundingRateInterval,
117 MarketSession,
118 EmaPrice,
119 EmaConfidence,
120 FeedUpdateTimestamp,
121 }
123
124#[derive(Debug, Clone, Deserialize, strum::EnumIter)]
125#[serde(rename_all = "kebab-case")]
126pub enum AssetClass {
127 Crypto,
128 Fx,
129 Equity,
130 Metal,
131 Rates,
132 Nav,
133 Commodity,
134 FundingRate,
135 Eco,
136 Kalshi,
137}
138
139impl AssetClass {
140 pub fn as_str(&self) -> &'static str {
141 match self {
142 AssetClass::Crypto => "crypto",
143 AssetClass::Fx => "fx",
144 AssetClass::Equity => "equity",
145 AssetClass::Metal => "metal",
146 AssetClass::Rates => "rates",
147 AssetClass::Nav => "nav",
148 AssetClass::Commodity => "commodity",
149 AssetClass::FundingRate => "funding-rate",
150 AssetClass::Eco => "eco",
151 AssetClass::Kalshi => "kalshi",
152 }
153 }
154}
155
156enum ExponentFactor {
158 Mul(i64),
160 Div(i64),
162}
163
164impl ExponentFactor {
165 fn get(exponent: i16) -> Option<Self> {
166 if exponent >= 0 {
167 let exponent: u32 = exponent.try_into().ok()?;
168 Some(ExponentFactor::Div(10_i64.checked_pow(exponent)?))
169 } else {
170 let minus_exponent: u32 = exponent.checked_neg()?.try_into().ok()?;
171 Some(ExponentFactor::Mul(10_i64.checked_pow(minus_exponent)?))
172 }
173 }
174}
175
176pub fn parse_proto_json<M: MessageFull>(
177 json: &str,
178) -> Result<M, protobuf_json_mapping::ParseError> {
179 protobuf_json_mapping::parse_from_str_with_options::<M>(
180 json,
181 &protobuf_json_mapping::ParseOptions {
182 ignore_unknown_fields: true,
183 ..Default::default()
184 },
185 )
186}
187
188#[test]
189fn magics_in_big_endian() {
190 use crate::{
191 binary_update::BINARY_UPDATE_FORMAT_MAGIC,
192 message::format_magics_le::{
193 EVM_FORMAT_MAGIC, JSON_FORMAT_MAGIC, LE_ECDSA_FORMAT_MAGIC, LE_UNSIGNED_FORMAT_MAGIC,
194 SOLANA_FORMAT_MAGIC,
195 },
196 payload::PAYLOAD_FORMAT_MAGIC,
197 };
198
199 assert_eq!(u32::swap_bytes(BINARY_UPDATE_FORMAT_MAGIC), 1937213467);
203 assert_eq!(u32::swap_bytes(PAYLOAD_FORMAT_MAGIC), 1976813459);
204
205 assert_eq!(u32::swap_bytes(SOLANA_FORMAT_MAGIC), 3103857282);
206 assert_eq!(u32::swap_bytes(JSON_FORMAT_MAGIC), 2584795844);
207 assert_eq!(u32::swap_bytes(EVM_FORMAT_MAGIC), 706910618);
208 assert_eq!(u32::swap_bytes(LE_ECDSA_FORMAT_MAGIC), 3837609805);
209 assert_eq!(u32::swap_bytes(LE_UNSIGNED_FORMAT_MAGIC), 206398297);
210
211 for magic in [
212 BINARY_UPDATE_FORMAT_MAGIC,
213 PAYLOAD_FORMAT_MAGIC,
214 SOLANA_FORMAT_MAGIC,
215 JSON_FORMAT_MAGIC,
216 EVM_FORMAT_MAGIC,
217 LE_ECDSA_FORMAT_MAGIC,
218 LE_UNSIGNED_FORMAT_MAGIC,
219 ] {
220 assert_ne!(u32::swap_bytes(magic), magic);
222 }
223}
224
225#[test]
226fn parse_proto_json_works() {
227 use protobuf::descriptor::descriptor_proto::ExtensionRange;
228
229 let ts1 = parse_proto_json::<ExtensionRange>(r#"{"start":1,"end":2}"#).unwrap();
230 assert_eq!(ts1.start, Some(1));
231 assert_eq!(ts1.end, Some(2));
232
233 let ts2 = parse_proto_json::<ExtensionRange>(r#"{"start":3,"extra":5}"#).unwrap();
234 assert_eq!(ts2.start, Some(3));
235 assert_eq!(ts2.end, None);
236}