df_hyperliquid_core/live/subscription/
parse.rs1use crate::live::{BadSubscription, Subscription};
5use crate::markets::Interval;
6use crate::utils::{Address, Json};
7
8const KINDS: [&str; 27] = [
10 "allMids",
11 "notification",
12 "webData3",
13 "twapStates",
14 "clearinghouseState",
15 "openOrders",
16 "candle",
17 "l2Book",
18 "trades",
19 "orderUpdates",
20 "userEvents",
21 "userFills",
22 "userFundings",
23 "userNonFundingLedgerUpdates",
24 "activeAssetCtx",
25 "activeAssetData",
26 "userTwapSliceFills",
27 "userTwapHistory",
28 "bbo",
29 "spotState",
30 "allDexsClearinghouseState",
31 "allDexsAssetCtxs",
32 "outcomeMetaUpdates",
33 "fastAssetCtxs",
34 "assetCtxs",
35 "spotAssetCtxs",
36 "userHistoricalOrders",
37];
38
39impl Subscription {
40 pub fn parse(json: &Json) -> Result<Subscription, BadSubscription> {
43 if !matches!(json, Json::Object(_)) {
44 return Err(BadSubscription::NotAnObject);
45 }
46 let named = json.get("type").ok_or(BadSubscription::Missing { kind: "subscription", field: "type" })?;
47 let named = named.as_str().ok_or(BadSubscription::Wrong {
48 kind: "subscription",
49 field: "type",
50 expected: "a string",
51 })?;
52 let kind = KINDS
53 .into_iter()
54 .find(|kind| *kind == named)
55 .ok_or_else(|| BadSubscription::UnknownType(named.to_owned()))?;
56 let fields = Fields { json, kind };
57 Ok(match kind {
58 "allMids" => Subscription::AllMids { dex: fields.dex()? },
59 "notification" => Subscription::Notification { user: fields.user()? },
60 "webData3" => Subscription::WebData3 { user: fields.user()? },
61 "twapStates" => Subscription::TwapStates { user: fields.user()?, dex: fields.dex()? },
62 "clearinghouseState" => Subscription::ClearinghouseState { user: fields.user()?, dex: fields.dex()? },
63 "openOrders" => Subscription::OpenOrders { user: fields.user()?, dex: fields.dex()? },
64 "candle" => Subscription::Candle { coin: fields.coin()?, interval: fields.interval()? },
65 "l2Book" => Subscription::L2Book {
66 coin: fields.coin()?,
67 n_sig_figs: fields.small("nSigFigs")?,
68 mantissa: fields.small("mantissa")?,
69 fast: fields.flag("fast")?,
70 },
71 "trades" => Subscription::Trades { coin: fields.coin()? },
72 "orderUpdates" => Subscription::OrderUpdates { user: fields.user()? },
73 "userEvents" => Subscription::UserEvents { user: fields.user()? },
74 "userFills" => {
75 Subscription::UserFills { user: fields.user()?, aggregate_by_time: fields.flag("aggregateByTime")? }
76 }
77 "userFundings" => Subscription::UserFundings { user: fields.user()? },
78 "userNonFundingLedgerUpdates" => Subscription::UserNonFundingLedgerUpdates { user: fields.user()? },
79 "activeAssetCtx" => Subscription::ActiveAssetCtx { coin: fields.coin()? },
80 "activeAssetData" => Subscription::ActiveAssetData { user: fields.user()?, coin: fields.coin()? },
81 "userTwapSliceFills" => Subscription::UserTwapSliceFills { user: fields.user()? },
82 "userTwapHistory" => Subscription::UserTwapHistory { user: fields.user()? },
83 "bbo" => Subscription::Bbo { coin: fields.coin()? },
84 "spotState" => Subscription::SpotState {
85 user: fields.user()?,
86 ignore_portfolio_margin: fields.flag("ignorePortfolioMargin")?,
87 },
88 "allDexsClearinghouseState" => Subscription::AllDexsClearinghouseState { user: fields.user()? },
89 "allDexsAssetCtxs" => Subscription::AllDexsAssetCtxs,
90 "outcomeMetaUpdates" => Subscription::OutcomeMetaUpdates,
91 "fastAssetCtxs" => Subscription::FastAssetCtxs,
92 "assetCtxs" => Subscription::AssetCtxs { dex: fields.dex()? },
93 "spotAssetCtxs" => Subscription::SpotAssetCtxs,
94 "userHistoricalOrders" => Subscription::UserHistoricalOrders { user: fields.user()? },
95 other => return Err(BadSubscription::UnknownType(other.to_owned())),
96 })
97 }
98}
99
100struct Fields<'a> {
102 json: &'a Json,
103 kind: &'static str,
104}
105
106impl Fields<'_> {
107 fn wrong(&self, field: &'static str, expected: &'static str) -> BadSubscription {
108 BadSubscription::Wrong { kind: self.kind, field, expected }
109 }
110
111 fn present(&self, field: &str) -> Option<&Json> {
113 self.json.get(field).filter(|value| !matches!(value, Json::Null))
114 }
115
116 fn string(&self, field: &'static str) -> Result<&str, BadSubscription> {
117 let value = self.present(field).ok_or(BadSubscription::Missing { kind: self.kind, field })?;
118 value.as_str().ok_or_else(|| self.wrong(field, "a string"))
119 }
120
121 fn user(&self) -> Result<Address, BadSubscription> {
122 self.string("user")?.parse().map_err(|_| self.wrong("user", "an address"))
123 }
124
125 fn coin(&self) -> Result<String, BadSubscription> {
126 self.string("coin").map(ToOwned::to_owned)
127 }
128
129 fn interval(&self) -> Result<Interval, BadSubscription> {
130 Interval::parse(self.string("interval")?).ok_or_else(|| self.wrong("interval", "an interval the docs list"))
131 }
132
133 fn dex(&self) -> Result<String, BadSubscription> {
134 match self.present("dex") {
135 None => Ok(String::new()),
136 Some(value) => value.as_str().map(ToOwned::to_owned).ok_or_else(|| self.wrong("dex", "a string")),
137 }
138 }
139
140 fn flag(&self, field: &'static str) -> Result<bool, BadSubscription> {
141 match self.present(field) {
142 None => Ok(false),
143 Some(value) => value.as_bool().ok_or_else(|| self.wrong(field, "true or false")),
144 }
145 }
146
147 fn small(&self, field: &'static str) -> Result<Option<u8>, BadSubscription> {
148 match self.present(field) {
149 None => Ok(None),
150 Some(value) => value
151 .as_u64()
152 .and_then(|number| u8::try_from(number).ok())
153 .map(Some)
154 .ok_or_else(|| self.wrong(field, "a small whole number")),
155 }
156 }
157}
158
159#[cfg(test)]
160#[path = "tests/parse.rs"]
161mod tests;