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