df_hyperliquid_core/utils/json/
msgpack.rs1use crate::utils::json::Json;
5use crate::utils::sign::msgpack::{Map, Value};
6
7#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
9pub enum Unpackable {
10 #[error("{0} is not a whole number; the venue hashes decimals as strings, so write it in quotes")]
11 Fraction(String),
12 #[error("{0} does not fit in 64 bits")]
13 TooWide(String),
14 #[error("an action is an object, and this is {0}")]
15 NotAnObject(&'static str),
16}
17
18impl Json {
19 pub fn to_msgpack(&self) -> Result<Value, Unpackable> {
21 Ok(match self {
22 Json::Null => Value::Nil,
23 Json::Bool(value) => Value::Bool(*value),
24 Json::Number(digits) => whole(digits)?,
25 Json::String(text) => Value::from(text.clone()),
26 Json::Array(items) => Value::Array(items.iter().map(Json::to_msgpack).collect::<Result<_, _>>()?),
27 Json::Object(entries) => Value::Map(map_of(entries)?),
28 })
29 }
30
31 pub fn to_action(&self) -> Result<Map, Unpackable> {
33 match self {
34 Json::Object(entries) => map_of(entries),
35 other => Err(Unpackable::NotAnObject(other.kind())),
36 }
37 }
38}
39
40fn map_of(entries: &[(String, Json)]) -> Result<Map, Unpackable> {
41 let mut map = Map::new();
42 for (key, value) in entries {
43 map = map.put(key.clone(), value.to_msgpack()?);
44 }
45 Ok(map)
46}
47
48fn whole(digits: &str) -> Result<Value, Unpackable> {
52 if digits.contains(['.', 'e', 'E']) {
53 return Err(Unpackable::Fraction(digits.to_owned()));
54 }
55 match digits.strip_prefix('-') {
56 Some(_) => digits.parse::<i64>().map(Value::Int).map_err(|_| Unpackable::TooWide(digits.to_owned())),
57 None => digits.parse::<u64>().map(Value::Uint).map_err(|_| Unpackable::TooWide(digits.to_owned())),
58 }
59}
60
61#[cfg(test)]
62#[path = "tests/msgpack.rs"]
63mod tests;