use std::fmt;
use std::str::FromStr;
use serde::ser::{SerializeMap, SerializeSeq};
use serde::{Deserialize, Serialize};
pub(crate) fn deserialize_vec_or_empty_string<'de, D, T>(
deserializer: D,
) -> Result<Vec<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: Deserialize<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum WireValue<T> {
Sequence(Vec<T>),
String(String),
Null(()),
}
match WireValue::<T>::deserialize(deserializer)? {
WireValue::Sequence(values) => Ok(values),
WireValue::String(value) if value.is_empty() => Ok(Vec::new()),
WireValue::String(value) => Err(serde::de::Error::custom(format!(
"expected an array or empty string, got {value:?}"
))),
WireValue::Null(()) => Ok(Vec::new()),
}
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct OkxResponse<D> {
pub code: String,
pub msg: String,
pub data: D,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct NumberString(String);
impl NumberString {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn parse<T: FromStr>(&self) -> Result<T, T::Err> {
self.0.parse()
}
pub fn into_string(self) -> String {
self.0
}
#[cfg(feature = "rust-decimal")]
pub fn to_decimal(&self) -> Result<rust_decimal::Decimal, rust_decimal::Error> {
self.0.parse()
}
}
impl From<String> for NumberString {
fn from(s: String) -> Self {
NumberString(s)
}
}
impl From<&str> for NumberString {
fn from(s: &str) -> Self {
NumberString(s.to_owned())
}
}
impl AsRef<str> for NumberString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for NumberString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, Default)]
pub struct RawRequestParams {
fields: Vec<(String, ParamValue)>,
}
impl RawRequestParams {
pub fn new() -> Self {
Self::default()
}
pub fn param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.set(key.into(), ParamValue::String(value.into()));
self
}
pub fn bool_param(mut self, key: impl Into<String>, value: bool) -> Self {
self.set(key.into(), ParamValue::Bool(value));
self
}
pub fn string_list<I, S>(mut self, key: impl Into<String>, values: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.set(
key.into(),
ParamValue::StringList(values.into_iter().map(Into::into).collect()),
);
self
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
fn set(&mut self, key: String, value: ParamValue) {
if let Some((_, existing)) = self.fields.iter_mut().find(|(name, _)| name == &key) {
*existing = value;
} else {
self.fields.push((key, value));
}
}
}
impl Serialize for RawRequestParams {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(self.fields.len()))?;
for (key, value) in &self.fields {
map.serialize_entry(key, value)?;
}
map.end()
}
}
pub type RequestParams = RawRequestParams;
#[derive(Debug, Clone)]
enum ParamValue {
String(String),
Bool(bool),
StringList(Vec<String>),
}
impl Serialize for ParamValue {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::String(value) => serializer.serialize_str(value),
Self::Bool(value) => serializer.serialize_bool(*value),
Self::StringList(values) => {
let mut seq = serializer.serialize_seq(Some(values.len()))?;
for value in values {
seq.serialize_element(value)?;
}
seq.end()
}
}
}
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct RestRow {
#[serde(default, rename = "instType")]
pub inst_type: String,
#[serde(default, rename = "instId")]
pub inst_id: String,
#[serde(default, rename = "instFamily")]
pub inst_family: String,
#[serde(default)]
pub ccy: String,
#[serde(default, rename = "ordId")]
pub ord_id: String,
#[serde(default, rename = "clOrdId")]
pub cl_ord_id: String,
#[serde(default, rename = "algoId")]
pub algo_id: String,
#[serde(default, rename = "algoClOrdId")]
pub algo_cl_ord_id: String,
#[serde(default, rename = "quoteId")]
pub quote_id: String,
#[serde(default, rename = "reqId")]
pub req_id: String,
#[serde(default, rename = "productId")]
pub product_id: String,
#[serde(default, rename = "type")]
pub row_type: String,
#[serde(default)]
pub state: String,
#[serde(default)]
pub status: String,
#[serde(default)]
pub side: String,
#[serde(default)]
pub amt: NumberString,
#[serde(default)]
pub sz: NumberString,
#[serde(default)]
pub px: NumberString,
#[serde(default)]
pub rate: NumberString,
#[serde(default)]
pub bal: NumberString,
#[serde(default)]
pub avail_bal: NumberString,
#[serde(default)]
pub ts: NumberString,
#[serde(default, rename = "sCode")]
pub s_code: String,
#[serde(default, rename = "sMsg")]
pub s_msg: String,
}
macro_rules! string_enum {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident {
$( $(#[$vmeta:meta])* $variant:ident = $wire:literal ),* $(,)?
}
) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
$vis enum $name {
$( $(#[$vmeta])* $variant, )*
Unknown(String),
}
impl $name {
pub fn as_str(&self) -> &str {
match self {
$( $name::$variant => $wire, )*
$name::Unknown(s) => s.as_str(),
}
}
}
impl ::core::convert::From<&str> for $name {
fn from(s: &str) -> Self {
match s {
$( $wire => $name::$variant, )*
other => $name::Unknown(other.to_owned()),
}
}
}
impl ::core::fmt::Display for $name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.write_str(self.as_str())
}
}
impl ::serde::Serialize for $name {
fn serialize<S: ::serde::Serializer>(&self, ser: S) -> ::core::result::Result<S::Ok, S::Error> {
ser.serialize_str(self.as_str())
}
}
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D: ::serde::Deserializer<'de>>(de: D) -> ::core::result::Result<Self, D::Error> {
let s = <::std::string::String as ::serde::Deserialize>::deserialize(de)?;
::core::result::Result::Ok($name::from(s.as_str()))
}
}
};
}
string_enum! {
pub enum InstType {
Spot = "SPOT",
Margin = "MARGIN",
Swap = "SWAP",
Futures = "FUTURES",
Option = "OPTION",
Events = "EVENTS",
}
}
string_enum! {
pub enum OrderSide {
Buy = "buy",
Sell = "sell",
}
}
string_enum! {
pub enum OrderType {
Market = "market",
Limit = "limit",
PostOnly = "post_only",
Fok = "fok",
Ioc = "ioc",
OptimalLimitIoc = "optimal_limit_ioc",
}
}
string_enum! {
pub enum TradeMode {
Cash = "cash",
Cross = "cross",
Isolated = "isolated",
SpotIsolated = "spot_isolated",
}
}
string_enum! {
pub enum PositionSide {
Long = "long",
Short = "short",
Net = "net",
}
}
string_enum! {
pub enum OrderState {
Live = "live",
PartiallyFilled = "partially_filled",
Filled = "filled",
Canceled = "canceled",
MmpCanceled = "mmp_canceled",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn number_string_parses_and_preserves() {
let n = NumberString::from("1.005");
assert_eq!(n.as_str(), "1.005");
assert_eq!(n.parse::<f64>().unwrap(), 1.005);
assert_eq!(n.into_string(), "1.005");
}
#[test]
fn known_enum_value_round_trips() {
let v: InstType = serde_json::from_str("\"SWAP\"").unwrap();
assert_eq!(v, InstType::Swap);
assert_eq!(serde_json::to_string(&v).unwrap(), "\"SWAP\"");
}
#[test]
fn unknown_enum_value_is_preserved_not_an_error() {
let v: InstType = serde_json::from_str("\"FUTURE_THING\"").unwrap();
assert_eq!(v, InstType::Unknown("FUTURE_THING".to_owned()));
assert_eq!(serde_json::to_string(&v).unwrap(), "\"FUTURE_THING\"");
}
#[test]
fn raw_request_params_replace_duplicate_keys() {
let params = RawRequestParams::new()
.param("ccy", "BTC")
.param("ccy", "ETH");
assert_eq!(
serde_json::to_value(params).unwrap(),
serde_json::json!({"ccy": "ETH"})
);
}
}